CommonClient: Add a hints tab (#2392)
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									779a312650
								
							
						
					
					
						commit
						ced35c5b78
					
				| 
						 | 
				
			
			@ -758,6 +758,7 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
 | 
			
		|||
        ctx.slot_info = {int(pid): data for pid, data in args["slot_info"].items()}
 | 
			
		||||
        ctx.hint_points = args.get("hint_points", 0)
 | 
			
		||||
        ctx.consume_players_package(args["players"])
 | 
			
		||||
        ctx.stored_data_notification_keys.add(f"_read_hints_{ctx.team}_{ctx.slot}")
 | 
			
		||||
        msgs = []
 | 
			
		||||
        if ctx.locations_checked:
 | 
			
		||||
            msgs.append({"cmd": "LocationChecks",
 | 
			
		||||
| 
						 | 
				
			
			@ -836,10 +837,14 @@ async def process_server_cmd(ctx: CommonContext, args: dict):
 | 
			
		|||
 | 
			
		||||
    elif cmd == "Retrieved":
 | 
			
		||||
        ctx.stored_data.update(args["keys"])
 | 
			
		||||
        if ctx.ui and f"_read_hints_{ctx.team}_{ctx.slot}" in args["keys"]:
 | 
			
		||||
            ctx.ui.update_hints()
 | 
			
		||||
 | 
			
		||||
    elif cmd == "SetReply":
 | 
			
		||||
        ctx.stored_data[args["key"]] = args["value"]
 | 
			
		||||
        if args["key"].startswith("EnergyLink"):
 | 
			
		||||
        if ctx.ui and f"_read_hints_{ctx.team}_{ctx.slot}" == args["key"]:
 | 
			
		||||
            ctx.ui.update_hints()
 | 
			
		||||
        elif args["key"].startswith("EnergyLink"):
 | 
			
		||||
            ctx.current_energy_link_value = args["value"]
 | 
			
		||||
            if ctx.ui:
 | 
			
		||||
                ctx.ui.set_new_energy_link_value()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,12 @@
 | 
			
		|||
    color: "FFFFFF"
 | 
			
		||||
<TabbedPanel>:
 | 
			
		||||
    tab_width: root.width / app.tab_count
 | 
			
		||||
<TooltipLabel>:
 | 
			
		||||
    text_size: self.width, None
 | 
			
		||||
    size_hint_y: None
 | 
			
		||||
    height: self.texture_size[1]
 | 
			
		||||
    font_size: dp(20)
 | 
			
		||||
    markup: True
 | 
			
		||||
<SelectableLabel>:
 | 
			
		||||
    canvas.before:
 | 
			
		||||
        Color:
 | 
			
		||||
| 
						 | 
				
			
			@ -24,11 +30,6 @@
 | 
			
		|||
        Rectangle:
 | 
			
		||||
            size: self.size
 | 
			
		||||
            pos: self.pos
 | 
			
		||||
    text_size: self.width, None
 | 
			
		||||
    size_hint_y: None
 | 
			
		||||
    height: self.texture_size[1]
 | 
			
		||||
    font_size: dp(20)
 | 
			
		||||
    markup: True
 | 
			
		||||
<UILog>:
 | 
			
		||||
    messages: 1000 # amount of messages stored in client logs.
 | 
			
		||||
    cols: 1
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +45,70 @@
 | 
			
		|||
        height: self.minimum_height
 | 
			
		||||
        orientation: 'vertical'
 | 
			
		||||
        spacing: dp(3)
 | 
			
		||||
<HintLabel>:
 | 
			
		||||
    canvas.before:
 | 
			
		||||
        Color:
 | 
			
		||||
            rgba: (.0, 0.9, .1, .3) if self.selected else (0.2, 0.2, 0.2, 1) if self.striped else (0.18, 0.18, 0.18, 1)
 | 
			
		||||
        Rectangle:
 | 
			
		||||
            size: self.size
 | 
			
		||||
            pos: self.pos
 | 
			
		||||
    height: self.minimum_height
 | 
			
		||||
    receiving_text: "Receiving Player"
 | 
			
		||||
    item_text: "Item"
 | 
			
		||||
    finding_text: "Finding Player"
 | 
			
		||||
    location_text: "Location"
 | 
			
		||||
    entrance_text: "Entrance"
 | 
			
		||||
    found_text: "Found?"
 | 
			
		||||
    TooltipLabel:
 | 
			
		||||
        id: receiving
 | 
			
		||||
        text: root.receiving_text
 | 
			
		||||
        halign: 'center'
 | 
			
		||||
        valign: 'center'
 | 
			
		||||
        pos_hint: {"center_y": 0.5}
 | 
			
		||||
    TooltipLabel:
 | 
			
		||||
        id: item
 | 
			
		||||
        text: root.item_text
 | 
			
		||||
        halign: 'center'
 | 
			
		||||
        valign: 'center'
 | 
			
		||||
        pos_hint: {"center_y": 0.5}
 | 
			
		||||
    TooltipLabel:
 | 
			
		||||
        id: finding
 | 
			
		||||
        text: root.finding_text
 | 
			
		||||
        halign: 'center'
 | 
			
		||||
        valign: 'center'
 | 
			
		||||
        pos_hint: {"center_y": 0.5}
 | 
			
		||||
    TooltipLabel:
 | 
			
		||||
        id: location
 | 
			
		||||
        text: root.location_text
 | 
			
		||||
        halign: 'center'
 | 
			
		||||
        valign: 'center'
 | 
			
		||||
        pos_hint: {"center_y": 0.5}
 | 
			
		||||
    TooltipLabel:
 | 
			
		||||
        id: entrance
 | 
			
		||||
        text: root.entrance_text
 | 
			
		||||
        halign: 'center'
 | 
			
		||||
        valign: 'center'
 | 
			
		||||
        pos_hint: {"center_y": 0.5}
 | 
			
		||||
    TooltipLabel:
 | 
			
		||||
        id: found
 | 
			
		||||
        text: root.found_text
 | 
			
		||||
        halign: 'center'
 | 
			
		||||
        valign: 'center'
 | 
			
		||||
        pos_hint: {"center_y": 0.5}
 | 
			
		||||
<HintLog>:
 | 
			
		||||
    cols: 1
 | 
			
		||||
    viewclass: 'HintLabel'
 | 
			
		||||
    scroll_y: self.height
 | 
			
		||||
    scroll_type: ["content", "bars"]
 | 
			
		||||
    bar_width: dp(12)
 | 
			
		||||
    effect_cls: "ScrollEffect"
 | 
			
		||||
    SelectableRecycleBoxLayout:
 | 
			
		||||
        default_size: None, dp(20)
 | 
			
		||||
        default_size_hint: 1, None
 | 
			
		||||
        size_hint_y: None
 | 
			
		||||
        height: self.minimum_height
 | 
			
		||||
        orientation: 'vertical'
 | 
			
		||||
        spacing: dp(3)
 | 
			
		||||
<ServerLabel>:
 | 
			
		||||
    text: "Server:"
 | 
			
		||||
    size_hint_x: None
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										263
									
								
								kvui.py
								
								
								
								
							
							
						
						
									
										263
									
								
								kvui.py
								
								
								
								
							| 
						 | 
				
			
			@ -5,12 +5,13 @@ import typing
 | 
			
		|||
 | 
			
		||||
if sys.platform == "win32":
 | 
			
		||||
    import ctypes
 | 
			
		||||
 | 
			
		||||
    # kivy 2.2.0 introduced DPI awareness on Windows, but it makes the UI enter an infinitely recursive re-layout
 | 
			
		||||
    # by setting the application to not DPI Aware, Windows handles scaling the entire window on its own, ignoring kivy's
 | 
			
		||||
    try:
 | 
			
		||||
        ctypes.windll.shcore.SetProcessDpiAwareness(0)
 | 
			
		||||
    except FileNotFoundError:  # shcore may not be found on <= Windows 7
 | 
			
		||||
        pass # TODO: remove silent except when Python 3.8 is phased out.
 | 
			
		||||
        pass  # TODO: remove silent except when Python 3.8 is phased out.
 | 
			
		||||
 | 
			
		||||
os.environ["KIVY_NO_CONSOLELOG"] = "1"
 | 
			
		||||
os.environ["KIVY_NO_FILELOG"] = "1"
 | 
			
		||||
| 
						 | 
				
			
			@ -18,14 +19,15 @@ os.environ["KIVY_NO_ARGS"] = "1"
 | 
			
		|||
os.environ["KIVY_LOG_ENABLE"] = "0"
 | 
			
		||||
 | 
			
		||||
import Utils
 | 
			
		||||
 | 
			
		||||
if Utils.is_frozen():
 | 
			
		||||
    os.environ["KIVY_DATA_DIR"] = Utils.local_path("data")
 | 
			
		||||
 | 
			
		||||
from kivy.config import Config
 | 
			
		||||
 | 
			
		||||
Config.set("input", "mouse", "mouse,disable_multitouch")
 | 
			
		||||
Config.set('kivy', 'exit_on_escape', '0')
 | 
			
		||||
Config.set('graphics', 'multisamples', '0')  # multisamples crash old intel drivers
 | 
			
		||||
Config.set("kivy", "exit_on_escape", "0")
 | 
			
		||||
Config.set("graphics", "multisamples", "0")  # multisamples crash old intel drivers
 | 
			
		||||
 | 
			
		||||
from kivy.app import App
 | 
			
		||||
from kivy.core.window import Window
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +60,6 @@ from kivy.uix.popup import Popup
 | 
			
		|||
 | 
			
		||||
fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from NetUtils import JSONtoTextParser, JSONMessagePart, SlotType
 | 
			
		||||
from Utils import async_start
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,8 +78,8 @@ class HoverBehavior(object):
 | 
			
		|||
    border_point = ObjectProperty(None)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        self.register_event_type('on_enter')
 | 
			
		||||
        self.register_event_type('on_leave')
 | 
			
		||||
        self.register_event_type("on_enter")
 | 
			
		||||
        self.register_event_type("on_leave")
 | 
			
		||||
        Window.bind(mouse_pos=self.on_mouse_pos)
 | 
			
		||||
        Window.bind(on_cursor_leave=self.on_cursor_leave)
 | 
			
		||||
        super(HoverBehavior, self).__init__(**kwargs)
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +107,7 @@ class HoverBehavior(object):
 | 
			
		|||
        self.dispatch("on_leave")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Factory.register('HoverBehavior', HoverBehavior)
 | 
			
		||||
Factory.register("HoverBehavior", HoverBehavior)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ToolTip(Label):
 | 
			
		||||
| 
						 | 
				
			
			@ -121,6 +122,60 @@ class HovererableLabel(HoverBehavior, Label):
 | 
			
		|||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TooltipLabel(HovererableLabel):
 | 
			
		||||
    tooltip = None
 | 
			
		||||
 | 
			
		||||
    def create_tooltip(self, text, x, y):
 | 
			
		||||
        text = text.replace("<br>", "\n").replace("&", "&").replace("&bl;", "[").replace("&br;", "]")
 | 
			
		||||
        if self.tooltip:
 | 
			
		||||
            # update
 | 
			
		||||
            self.tooltip.children[0].text = text
 | 
			
		||||
        else:
 | 
			
		||||
            self.tooltip = FloatLayout()
 | 
			
		||||
            tooltip_label = ToolTip(text=text)
 | 
			
		||||
            self.tooltip.add_widget(tooltip_label)
 | 
			
		||||
            fade_in_animation.start(self.tooltip)
 | 
			
		||||
            App.get_running_app().root.add_widget(self.tooltip)
 | 
			
		||||
 | 
			
		||||
        # handle left-side boundary to not render off-screen
 | 
			
		||||
        x = max(x, 3 + self.tooltip.children[0].texture_size[0] / 2)
 | 
			
		||||
 | 
			
		||||
        # position float layout
 | 
			
		||||
        self.tooltip.x = x - self.tooltip.width / 2
 | 
			
		||||
        self.tooltip.y = y - self.tooltip.height / 2 + 48
 | 
			
		||||
 | 
			
		||||
    def remove_tooltip(self):
 | 
			
		||||
        if self.tooltip:
 | 
			
		||||
            App.get_running_app().root.remove_widget(self.tooltip)
 | 
			
		||||
            self.tooltip = None
 | 
			
		||||
 | 
			
		||||
    def on_mouse_pos(self, window, pos):
 | 
			
		||||
        if not self.get_root_window():
 | 
			
		||||
            return  # Abort if not displayed
 | 
			
		||||
        super().on_mouse_pos(window, pos)
 | 
			
		||||
        if self.refs and self.hovered:
 | 
			
		||||
 | 
			
		||||
            tx, ty = self.to_widget(*pos, relative=True)
 | 
			
		||||
            # Why TF is Y flipped *within* the texture?
 | 
			
		||||
            ty = self.texture_size[1] - ty
 | 
			
		||||
            hit = False
 | 
			
		||||
            for uid, zones in self.refs.items():
 | 
			
		||||
                for zone in zones:
 | 
			
		||||
                    x, y, w, h = zone
 | 
			
		||||
                    if x <= tx <= w and y <= ty <= h:
 | 
			
		||||
                        self.create_tooltip(uid.split("|", 1)[1], *pos)
 | 
			
		||||
                        hit = True
 | 
			
		||||
                        break
 | 
			
		||||
            if not hit:
 | 
			
		||||
                self.remove_tooltip()
 | 
			
		||||
 | 
			
		||||
    def on_enter(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def on_leave(self):
 | 
			
		||||
        self.remove_tooltip()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServerLabel(HovererableLabel):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super(HovererableLabel, self).__init__(*args, **kwargs)
 | 
			
		||||
| 
						 | 
				
			
			@ -189,11 +244,10 @@ class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
 | 
			
		|||
    """ Adds selection and focus behaviour to the view. """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SelectableLabel(RecycleDataViewBehavior, HovererableLabel):
 | 
			
		||||
class SelectableLabel(RecycleDataViewBehavior, TooltipLabel):
 | 
			
		||||
    """ Add selection support to the Label """
 | 
			
		||||
    index = None
 | 
			
		||||
    selected = BooleanProperty(False)
 | 
			
		||||
    tooltip = None
 | 
			
		||||
 | 
			
		||||
    def refresh_view_attrs(self, rv, index, data):
 | 
			
		||||
        """ Catch and handle the view changes """
 | 
			
		||||
| 
						 | 
				
			
			@ -201,56 +255,6 @@ class SelectableLabel(RecycleDataViewBehavior, HovererableLabel):
 | 
			
		|||
        return super(SelectableLabel, self).refresh_view_attrs(
 | 
			
		||||
            rv, index, data)
 | 
			
		||||
 | 
			
		||||
    def create_tooltip(self, text, x, y):
 | 
			
		||||
        text = text.replace("<br>", "\n").replace('&', '&').replace('&bl;', '[').replace('&br;', ']')
 | 
			
		||||
        if self.tooltip:
 | 
			
		||||
            # update
 | 
			
		||||
            self.tooltip.children[0].text = text
 | 
			
		||||
        else:
 | 
			
		||||
            self.tooltip = FloatLayout()
 | 
			
		||||
            tooltip_label = ToolTip(text=text)
 | 
			
		||||
            self.tooltip.add_widget(tooltip_label)
 | 
			
		||||
            fade_in_animation.start(self.tooltip)
 | 
			
		||||
            App.get_running_app().root.add_widget(self.tooltip)
 | 
			
		||||
 | 
			
		||||
        # handle left-side boundary to not render off-screen
 | 
			
		||||
        x = max(x, 3+self.tooltip.children[0].texture_size[0] / 2)
 | 
			
		||||
 | 
			
		||||
        # position float layout
 | 
			
		||||
        self.tooltip.x = x - self.tooltip.width / 2
 | 
			
		||||
        self.tooltip.y = y - self.tooltip.height / 2 + 48
 | 
			
		||||
 | 
			
		||||
    def remove_tooltip(self):
 | 
			
		||||
        if self.tooltip:
 | 
			
		||||
            App.get_running_app().root.remove_widget(self.tooltip)
 | 
			
		||||
            self.tooltip = None
 | 
			
		||||
 | 
			
		||||
    def on_mouse_pos(self, window, pos):
 | 
			
		||||
        if not self.get_root_window():
 | 
			
		||||
            return  # Abort if not displayed
 | 
			
		||||
        super().on_mouse_pos(window, pos)
 | 
			
		||||
        if self.refs and self.hovered:
 | 
			
		||||
 | 
			
		||||
            tx, ty = self.to_widget(*pos, relative=True)
 | 
			
		||||
            # Why TF is Y flipped *within* the texture?
 | 
			
		||||
            ty = self.texture_size[1] - ty
 | 
			
		||||
            hit = False
 | 
			
		||||
            for uid, zones in self.refs.items():
 | 
			
		||||
                for zone in zones:
 | 
			
		||||
                    x, y, w, h = zone
 | 
			
		||||
                    if x <= tx <= w and y <= ty <= h:
 | 
			
		||||
                        self.create_tooltip(uid.split("|", 1)[1], *pos)
 | 
			
		||||
                        hit = True
 | 
			
		||||
                        break
 | 
			
		||||
            if not hit:
 | 
			
		||||
                self.remove_tooltip()
 | 
			
		||||
 | 
			
		||||
    def on_enter(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def on_leave(self):
 | 
			
		||||
        self.remove_tooltip()
 | 
			
		||||
 | 
			
		||||
    def on_touch_down(self, touch):
 | 
			
		||||
        """ Add selection on touch down """
 | 
			
		||||
        if super(SelectableLabel, self).on_touch_down(touch):
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +278,7 @@ class SelectableLabel(RecycleDataViewBehavior, HovererableLabel):
 | 
			
		|||
                elif not cmdinput.text and text.startswith("Missing: "):
 | 
			
		||||
                    cmdinput.text = text.replace("Missing: ", "!hint_location ")
 | 
			
		||||
 | 
			
		||||
                Clipboard.copy(text.replace('&', '&').replace('&bl;', '[').replace('&br;', ']'))
 | 
			
		||||
                Clipboard.copy(text.replace("&", "&").replace("&bl;", "[").replace("&br;", "]"))
 | 
			
		||||
                return self.parent.select_with_touch(self.index, touch)
 | 
			
		||||
 | 
			
		||||
    def apply_selection(self, rv, index, is_selected):
 | 
			
		||||
| 
						 | 
				
			
			@ -282,9 +286,68 @@ class SelectableLabel(RecycleDataViewBehavior, HovererableLabel):
 | 
			
		|||
        self.selected = is_selected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HintLabel(RecycleDataViewBehavior, BoxLayout):
 | 
			
		||||
    selected = BooleanProperty(False)
 | 
			
		||||
    striped = BooleanProperty(False)
 | 
			
		||||
    index = None
 | 
			
		||||
    no_select = []
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(HintLabel, self).__init__()
 | 
			
		||||
        self.receiving_text = ""
 | 
			
		||||
        self.item_text = ""
 | 
			
		||||
        self.finding_text = ""
 | 
			
		||||
        self.location_text = ""
 | 
			
		||||
        self.entrance_text = ""
 | 
			
		||||
        self.found_text = ""
 | 
			
		||||
        for child in self.children:
 | 
			
		||||
            child.bind(texture_size=self.set_height)
 | 
			
		||||
 | 
			
		||||
    def set_height(self, instance, value):
 | 
			
		||||
        self.height = max([child.texture_size[1] for child in self.children])
 | 
			
		||||
 | 
			
		||||
    def refresh_view_attrs(self, rv, index, data):
 | 
			
		||||
        self.index = index
 | 
			
		||||
        if "select" in data and not data["select"] and index not in self.no_select:
 | 
			
		||||
            self.no_select.append(index)
 | 
			
		||||
        self.striped = data["striped"]
 | 
			
		||||
        self.receiving_text = data["receiving"]["text"]
 | 
			
		||||
        self.item_text = data["item"]["text"]
 | 
			
		||||
        self.finding_text = data["finding"]["text"]
 | 
			
		||||
        self.location_text = data["location"]["text"]
 | 
			
		||||
        self.entrance_text = data["entrance"]["text"]
 | 
			
		||||
        self.found_text = data["found"]["text"]
 | 
			
		||||
        self.height = self.minimum_height
 | 
			
		||||
        return super(HintLabel, self).refresh_view_attrs(rv, index, data)
 | 
			
		||||
 | 
			
		||||
    def on_touch_down(self, touch):
 | 
			
		||||
        """ Add selection on touch down """
 | 
			
		||||
        if super(HintLabel, self).on_touch_down(touch):
 | 
			
		||||
            return True
 | 
			
		||||
        if self.index not in self.no_select:
 | 
			
		||||
            if self.collide_point(*touch.pos):
 | 
			
		||||
                if self.selected:
 | 
			
		||||
                    self.parent.clear_selection()
 | 
			
		||||
                else:
 | 
			
		||||
                    text = "".join([self.receiving_text, "\'s ", self.item_text, " is at ", self.location_text, " in ",
 | 
			
		||||
                                    self.finding_text, "\'s World", (" at " + self.entrance_text)
 | 
			
		||||
                                    if self.entrance_text != "Vanilla"
 | 
			
		||||
                                    else "", ". (", self.found_text.lower(), ")"])
 | 
			
		||||
                    temp = MarkupLabel(text).markup
 | 
			
		||||
                    text = "".join(
 | 
			
		||||
                        part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]")))
 | 
			
		||||
                    Clipboard.copy(escape_markup(text).replace("&", "&").replace("&bl;", "[").replace("&br;", "]"))
 | 
			
		||||
                    return self.parent.select_with_touch(self.index, touch)
 | 
			
		||||
 | 
			
		||||
    def apply_selection(self, rv, index, is_selected):
 | 
			
		||||
        """ Respond to the selection of items in the view. """
 | 
			
		||||
        if self.index not in self.no_select:
 | 
			
		||||
            self.selected = is_selected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectBarTextInput(TextInput):
 | 
			
		||||
    def insert_text(self, substring, from_undo=False):
 | 
			
		||||
        s = substring.replace('\n', '').replace('\r', '')
 | 
			
		||||
        s = substring.replace("\n", "").replace("\r", "")
 | 
			
		||||
        return super(ConnectBarTextInput, self).insert_text(s, from_undo=from_undo)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -302,7 +365,7 @@ class MessageBox(Popup):
 | 
			
		|||
    def __init__(self, title, text, error=False, **kwargs):
 | 
			
		||||
        label = MessageBox.MessageBoxLabel(text=text)
 | 
			
		||||
        separator_color = [217 / 255, 129 / 255, 122 / 255, 1.] if error else [47 / 255., 167 / 255., 212 / 255, 1.]
 | 
			
		||||
        super().__init__(title=title, content=label, size_hint=(None, None), width=max(100, int(label.width)+40),
 | 
			
		||||
        super().__init__(title=title, content=label, size_hint=(None, None), width=max(100, int(label.width) + 40),
 | 
			
		||||
                         separator_color=separator_color, **kwargs)
 | 
			
		||||
        self.height += max(0, label.height - 18)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -358,11 +421,14 @@ class GameManager(App):
 | 
			
		|||
        # top part
 | 
			
		||||
        server_label = ServerLabel()
 | 
			
		||||
        self.connect_layout.add_widget(server_label)
 | 
			
		||||
        self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:", size_hint_y=None,
 | 
			
		||||
        self.server_connect_bar = ConnectBarTextInput(text=self.ctx.suggested_address or "archipelago.gg:",
 | 
			
		||||
                                                      size_hint_y=None,
 | 
			
		||||
                                                      height=dp(30), multiline=False, write_tab=False)
 | 
			
		||||
 | 
			
		||||
        def connect_bar_validate(sender):
 | 
			
		||||
            if not self.ctx.server:
 | 
			
		||||
                self.connect_button_action(sender)
 | 
			
		||||
 | 
			
		||||
        self.server_connect_bar.bind(on_text_validate=connect_bar_validate)
 | 
			
		||||
        self.connect_layout.add_widget(self.server_connect_bar)
 | 
			
		||||
        self.server_connect_button = Button(text="Connect", size=(dp(100), dp(30)), size_hint_y=None, size_hint_x=None)
 | 
			
		||||
| 
						 | 
				
			
			@ -383,20 +449,22 @@ class GameManager(App):
 | 
			
		|||
            bridge_logger = logging.getLogger(logger_name)
 | 
			
		||||
            panel = TabbedPanelItem(text=display_name)
 | 
			
		||||
            self.log_panels[display_name] = panel.content = UILog(bridge_logger)
 | 
			
		||||
            self.tabs.add_widget(panel)
 | 
			
		||||
            if len(self.logging_pairs) > 1:
 | 
			
		||||
                # show Archipelago tab if other logging is present
 | 
			
		||||
                self.tabs.add_widget(panel)
 | 
			
		||||
 | 
			
		||||
        hint_panel = TabbedPanelItem(text="Hints")
 | 
			
		||||
        self.log_panels["Hints"] = hint_panel.content = HintLog(self.json_to_kivy_parser)
 | 
			
		||||
        self.tabs.add_widget(hint_panel)
 | 
			
		||||
 | 
			
		||||
        if len(self.logging_pairs) == 1:
 | 
			
		||||
            self.tabs.default_tab_text = "Archipelago"
 | 
			
		||||
 | 
			
		||||
        self.main_area_container = GridLayout(size_hint_y=1, rows=1)
 | 
			
		||||
        self.main_area_container.add_widget(self.tabs)
 | 
			
		||||
 | 
			
		||||
        self.grid.add_widget(self.main_area_container)
 | 
			
		||||
 | 
			
		||||
        if len(self.logging_pairs) == 1:
 | 
			
		||||
            # Hide Tab selection if only one tab
 | 
			
		||||
            self.tabs.clear_tabs()
 | 
			
		||||
            self.tabs.do_default_tab = False
 | 
			
		||||
            self.tabs.current_tab.height = 0
 | 
			
		||||
            self.tabs.tab_height = 0
 | 
			
		||||
 | 
			
		||||
        # bottom part
 | 
			
		||||
        bottom_layout = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(30))
 | 
			
		||||
        info_button = Button(size=(dp(100), dp(30)), text="Command:", size_hint_x=None)
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +490,7 @@ class GameManager(App):
 | 
			
		|||
        return self.container
 | 
			
		||||
 | 
			
		||||
    def update_texts(self, dt):
 | 
			
		||||
        if hasattr(self.tabs.content.children[0], 'fix_heights'):
 | 
			
		||||
        if hasattr(self.tabs.content.children[0], "fix_heights"):
 | 
			
		||||
            self.tabs.content.children[0].fix_heights()  # TODO: remove this when Kivy fixes this upstream
 | 
			
		||||
        if self.ctx.server:
 | 
			
		||||
            self.title = self.base_title + " " + Utils.__version__ + \
 | 
			
		||||
| 
						 | 
				
			
			@ -499,6 +567,10 @@ class GameManager(App):
 | 
			
		|||
        if hasattr(self, "energy_link_label"):
 | 
			
		||||
            self.energy_link_label.text = f"EL: {Utils.format_SI_prefix(self.ctx.current_energy_link_value)}J"
 | 
			
		||||
 | 
			
		||||
    def update_hints(self):
 | 
			
		||||
        hints = self.ctx.stored_data[f"_read_hints_{self.ctx.team}_{self.ctx.slot}"]
 | 
			
		||||
        self.log_panels["Hints"].refresh_hints(hints)
 | 
			
		||||
 | 
			
		||||
    # default F1 keybind, opens a settings menu, that seems to break the layout engine once closed
 | 
			
		||||
    def open_settings(self, *largs):
 | 
			
		||||
        pass
 | 
			
		||||
| 
						 | 
				
			
			@ -513,12 +585,12 @@ class LogtoUI(logging.Handler):
 | 
			
		|||
    def format_compact(record: logging.LogRecord) -> str:
 | 
			
		||||
        if isinstance(record.msg, Exception):
 | 
			
		||||
            return str(record.msg)
 | 
			
		||||
        return (f'{record.exc_info[1]}\n' if record.exc_info else '') + str(record.msg).split("\n")[0]
 | 
			
		||||
        return (f"{record.exc_info[1]}\n" if record.exc_info else "") + str(record.msg).split("\n")[0]
 | 
			
		||||
 | 
			
		||||
    def handle(self, record: logging.LogRecord) -> None:
 | 
			
		||||
        if getattr(record, 'skip_gui', False):
 | 
			
		||||
        if getattr(record, "skip_gui", False):
 | 
			
		||||
            pass  # skip output
 | 
			
		||||
        elif getattr(record, 'compact_gui', False):
 | 
			
		||||
        elif getattr(record, "compact_gui", False):
 | 
			
		||||
            self.on_log(self.format_compact(record))
 | 
			
		||||
        else:
 | 
			
		||||
            self.on_log(self.format(record))
 | 
			
		||||
| 
						 | 
				
			
			@ -552,6 +624,44 @@ class UILog(RecycleView):
 | 
			
		|||
                element.height = element.texture_size[1]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HintLog(RecycleView):
 | 
			
		||||
    header = {
 | 
			
		||||
        "receiving": {"text": "[u]Receiving Player[/u]"},
 | 
			
		||||
        "item": {"text": "[u]Item[/u]"},
 | 
			
		||||
        "finding": {"text": "[u]Finding Player[/u]"},
 | 
			
		||||
        "location": {"text": "[u]Location[/u]"},
 | 
			
		||||
        "entrance": {"text": "[u]Entrance[/u]"},
 | 
			
		||||
        "found": {"text": "[u]Status[/u]"},
 | 
			
		||||
        "striped": True,
 | 
			
		||||
        "select": False,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def __init__(self, parser):
 | 
			
		||||
        super(HintLog, self).__init__()
 | 
			
		||||
        self.data = [self.header]
 | 
			
		||||
        self.parser = parser
 | 
			
		||||
 | 
			
		||||
    def refresh_hints(self, hints):
 | 
			
		||||
        self.data = [self.header]
 | 
			
		||||
        striped = False
 | 
			
		||||
        for hint in hints:
 | 
			
		||||
            self.data.append({
 | 
			
		||||
                "striped": striped,
 | 
			
		||||
                "receiving": {"text": self.parser.handle_node({"type": "player_id", "text": hint["receiving_player"]})},
 | 
			
		||||
                "item": {"text": self.parser.handle_node(
 | 
			
		||||
                    {"type": "item_id", "text": hint["item"], "flags": hint["item_flags"]})},
 | 
			
		||||
                "finding": {"text": self.parser.handle_node({"type": "player_id", "text": hint["finding_player"]})},
 | 
			
		||||
                "location": {"text": self.parser.handle_node({"type": "location_id", "text": hint["location"]})},
 | 
			
		||||
                "entrance": {"text": self.parser.handle_node({"type": "color" if hint["entrance"] else "text",
 | 
			
		||||
                                                              "color": "blue", "text": hint["entrance"]
 | 
			
		||||
                                                              if hint["entrance"] else "Vanilla"})},
 | 
			
		||||
                "found": {
 | 
			
		||||
                    "text": self.parser.handle_node({"type": "color", "color": "green" if hint["found"] else "red",
 | 
			
		||||
                                                     "text": "Found" if hint["found"] else "Not Found"})},
 | 
			
		||||
            })
 | 
			
		||||
            striped = not striped
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class E(ExceptionHandler):
 | 
			
		||||
    logger = logging.getLogger("Client")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -599,7 +709,7 @@ class KivyJSONtoTextParser(JSONtoTextParser):
 | 
			
		|||
                   f"Type: {SlotType(slot_info.type).name}"
 | 
			
		||||
            if slot_info.group_members:
 | 
			
		||||
                text += f"<br>Members:<br> " + \
 | 
			
		||||
                        '<br> '.join(self.ctx.player_names[player] for player in slot_info.group_members)
 | 
			
		||||
                        "<br> ".join(self.ctx.player_names[player] for player in slot_info.group_members)
 | 
			
		||||
            node.setdefault("refs", []).append(text)
 | 
			
		||||
        return super(KivyJSONtoTextParser, self)._handle_player_id(node)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -627,4 +737,3 @@ user_file = Utils.user_path("data", "user.kv")
 | 
			
		|||
if os.path.exists(user_file):
 | 
			
		||||
    logging.info("Loading user.kv into builder.")
 | 
			
		||||
    Builder.load_file(user_file)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue