コード例 #1
0
        public ServerTab()
        {
            // Generate the chat and user list
            chatMessageList = new ChatMessageList();
            userList        = new UserList(() => userSearch);
            messageBox      = new TextFieldWidget(
                initialText: message,
                onChange: newMessage => message = newMessage
                );

            // Create a tab container to hold the chat and trade list
            contents = new TabsContainer();

            // Create a flex container to hold the chat tab content
            HorizontalFlexContainer chatRow = new HorizontalFlexContainer(DEFAULT_SPACING);

            // Chat container
            chatRow.Add(
                GenerateChat()
                );

            // Right column (settings and user list) container
            chatRow.Add(
                new Container(
                    GenerateRightColumn(),
                    width: RIGHT_COLUMN_CONTAINER_WIDTH
                    )
                );

            // Add the chat row as a tab
            contents.AddTab("Phinix_tabs_chat".Translate(), chatRow);

            // Add the active trades tab
            contents.AddTab("Phinix_tabs_trades".Translate(), new TradeList());
        }
コード例 #2
0
        /// <summary>
        /// Extends the default window drawing behaviour by drawing the Phinix chat interface.
        /// </summary>
        /// <param name="inRect">Container to draw within</param>
        public override void DoWindowContents(Rect inRect)
        {
            base.DoWindowContents(inRect);

            // Create a tab container to hold the chat and trade list
            TabsContainer tabContainer = new TabsContainer(newTabIndex => currentTabIndex = newTabIndex, currentTabIndex);

            // Create a flex container to hold the chat tab content
            HorizontalFlexContainer chatRow = new HorizontalFlexContainer(DEFAULT_SPACING);

            // Chat container
            chatRow.Add(
                GenerateChat()
                );

            // Right column (settings and user list) container
            chatRow.Add(
                new Container(
                    GenerateRightColumn(),
                    width: RIGHT_COLUMN_CONTAINER_WIDTH
                    )
                );

            // Add the chat row as a tab
            tabContainer.AddTab("Phinix_tabs_chat".Translate(), chatRow);

            // Add the active trades tab
            tabContainer.AddTab("Phinix_tabs_trades".Translate(), GenerateTradeRows());

            // Draw the tabs
            tabContainer.Draw(inRect);
        }
コード例 #3
0
        /// <summary>
        /// Generates a non-editable server address and disconnect button.
        /// </summary>
        /// <returns><see cref="HorizontalFlexContainer"/> containing connected server details</returns>
        private HorizontalFlexContainer GenerateConnectedServerDetails()
        {
            // Create a flex container as our 'row' to store elements in
            HorizontalFlexContainer row = new HorizontalFlexContainer();

            // Server address label
            row.Add(
                new TextWidget(
                    text: "Phinix_settings_connectedToLabel".Translate(serverAddress),
                    anchor: TextAnchor.MiddleLeft
                    )
                );

            // Disconnect button
            row.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_settings_disconnectButton".Translate(),
                        clickAction: () => Client.Instance.Disconnect()
                        ),
                    width: CONNECT_BUTTON_WIDTH
                    )
                );

            // Return the generated row
            return(row);
        }
コード例 #4
0
        /// <summary>
        /// Generates an editable display name field and a button to apply the changes.
        /// </summary>
        /// <returns><see cref="HorizontalFlexContainer"/> containing an editable display name field and a button to apply the changes</returns>
        private HorizontalFlexContainer GenerateEditableDisplayName()
        {
            // Create a flex container as our 'row' to store the editable name field in
            HorizontalFlexContainer row = new HorizontalFlexContainer();

            // Editable display name text box
            row.Add(
                new TextFieldWidget(
                    text: Client.Instance.DisplayName,
                    onChange: newDisplayName => Client.Instance.DisplayName = newDisplayName
                    )
                );

            // Set display name button
            row.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_settings_setDisplayNameButton".Translate(),
                        clickAction: () => Client.Instance.UpdateDisplayName(Client.Instance.DisplayName)
                        ),
                    width: DISPLAY_NAME_SET_BUTTON_WIDTH
                    )
                );

            // Return the generated row
            return(row);
        }
コード例 #5
0
ファイル: SettingsWindow.cs プロジェクト: thomotron/Phinix
        /// <summary>
        /// Generates an editable display name field, a button to apply the changes, and a preview.
        /// </summary>
        /// <returns><see cref="Displayable"/> containing an editable display name field, a button to apply the changes, and a preview</returns>
        private Displayable GenerateEditableDisplayName()
        {
            // Make the name preview early so we can bind to it's update method
            DynamicTextWidget namePreview = new DynamicTextWidget(
                textCallback: () => "Phinix_settings_displayNamePreview".Translate(Client.Instance.DisplayName).Resolve(),
                wrap: false
                );

            // Create a column to store the editable portion and preview in
            VerticalFlexContainer column = new VerticalFlexContainer();

            // Create a flex container as our 'row' to store the editable name field in
            HorizontalFlexContainer editableRow = new HorizontalFlexContainer();

            // Editable display name text box
            editableRow.Add(
                new TextFieldWidget(
                    initialText: Client.Instance.DisplayName,
                    onChange: newDisplayName =>
            {
                Client.Instance.DisplayName = newDisplayName;
                namePreview.Update();
            }
                    )
                );

            // Set display name button
            editableRow.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_settings_setDisplayNameButton".Translate(),
                        clickAction: () => Client.Instance.UpdateDisplayName(Client.Instance.DisplayName)
                        ),
                    width: DISPLAY_NAME_SET_BUTTON_WIDTH
                    )
                );

            // Wrap the editable portion in a container to enforce height and add it to the column
            column.Add(
                new HeightContainer(
                    child: editableRow,
                    height: ROW_HEIGHT
                    )
                );

            // Display name preview
            column.Add(new HorizontalScrollContainer(namePreview));

            // Return the generated column
            return(column);
        }
コード例 #6
0
        /// <summary>
        /// Generates a <see cref="VerticalFlexContainer"/> the chat window consisting of a message list, message entry box, and a send button.
        /// </summary>
        private VerticalFlexContainer GenerateChat()
        {
            // Create a flex container to hold the column elements
            VerticalFlexContainer column = new VerticalFlexContainer(DEFAULT_SPACING);

            // Chat message area
            column.Add(
                new ConditionalContainer(
                    childIfTrue: chatMessageList,
                    childIfFalse: new PlaceholderWidget(
                        text: "Phinix_chat_pleaseLogInPlaceholder".Translate()
                        ),
                    condition: () => Instance.Online
                    )
                );

            // Create a flex container to hold the text field and button
            HorizontalFlexContainer messageEntryFlexContainer = new HorizontalFlexContainer();

            // Message entry field
            messageEntryFlexContainer.Add(messageBox);

            // Send button
            messageEntryFlexContainer.Add(
                new WidthContainer(
                    new ButtonWidget(
                        label: "Phinix_chat_sendButton".Translate(),
                        clickAction: sendChatMessage
                        ),
                    width: CHAT_SEND_BUTTON_WIDTH
                    )
                );

            // Add the flex container to the column
            column.Add(
                new HeightContainer(
                    messageEntryFlexContainer,
                    height: CHAT_TEXTBOX_HEIGHT
                    )
                );

            // Return the generated column
            return(column);
        }
コード例 #7
0
ファイル: TradeWindow.cs プロジェクト: Maatss/Phinix
        /// <summary>
        /// Generates a <see cref="VerticalFlexContainer"/> containing our available items.
        /// </summary>
        private VerticalFlexContainer GenerateAvailableItems()
        {
//            // Set the text anchor
//            TextAnchor oldAnchor = Text.Anchor;
//            Text.Anchor = TextAnchor.MiddleCenter;
//
//            // 'Sort by' label
//            Rect sortByLabelRect = new Rect(
//                x: container.xMin,
//                y: container.yMin,
//                width: Text.CalcSize("Phinix_trade_sortByLabel".Translate()).x,
//                height: SORT_HEIGHT
//            );
//            Widgets.Label(sortByLabelRect, "Phinix_trade_sortByLabel".Translate());
//
//            // Reset the text anchor
//            Text.Anchor = oldAnchor;
//
//            // First sorting preference
//            Rect firstSortButtonRect = new Rect(
//                x: sortByLabelRect.xMax + DEFAULT_SPACING,
//                y: container.yMin,
//                width: SORT_BUTTON_WIDTH,
//                height: SORT_HEIGHT
//            );
//            if (Widgets.ButtonText(firstSortButtonRect, "", active: false))
//            {
//                // TODO: Sorting
//            }
//
//            // Second sorting preference
//            Rect secondSortButtonRect = new Rect(
//                x: firstSortButtonRect.xMax + DEFAULT_SPACING,
//                y: container.yMin,
//                width: SORT_BUTTON_WIDTH,
//                height: SORT_HEIGHT
//            );
//            if (Widgets.ButtonText(secondSortButtonRect, "", active: false))
//            {
//                // TODO: Sorting
//            }

            // Create a new flex container as our 'column' to store everything in
            VerticalFlexContainer column = new VerticalFlexContainer(DEFAULT_SPACING);

            // Create a new flex container as our 'row' to store the search bar in
            HorizontalFlexContainer searchRow = new HorizontalFlexContainer(DEFAULT_SPACING);

            // Spacer to push everything to the right
            searchRow.Add(
                new SpacerWidget()
                );

            // Search label
            searchRow.Add(
                new Container(
                    new TextWidget(
                        text: "Phinix_trade_searchLabel".Translate(),
                        anchor: TextAnchor.MiddleCenter
                        ),
                    width: Text.CalcSize("Phinix_trade_searchLabel".Translate()).x
                    )
                );

            // Search text field
            searchRow.Add(
                new Container(
                    new TextFieldWidget(
                        text: search,
                        onChange: newSearch => search = newSearch
                        ),
                    width: SEARCH_TEXT_FIELD_WIDTH
                    )
                );

            // Add the search row to the main column
            column.Add(
                new Container(
                    searchRow,
                    height: SORT_HEIGHT
                    )
                );

            // Filter the item stacks list for only those containing the search text
            IEnumerable <StackedThings> filteredItemStacks = itemStacks.Where(itemStack =>
            {
                // Make sure the item stack has things in it
                if (itemStack.Things.Count == 0)
                {
                    return(false);
                }

                // Get the first thing from the item stack
                Thing firstThing = itemStack.Things.First();

                // Return whether the first thing's def label matches the search text
                return(firstThing.def.label.ToLower().Contains(search.ToLower()));
            });

            // Stockpile items list
            column.Add(
                GenerateItemList(filteredItemStacks, stockpileItemsScrollPos, newScrollPos => stockpileItemsScrollPos = newScrollPos, true)
                );

            // Return the generated flex container
            return(column);
        }
コード例 #8
0
ファイル: TradeWindow.cs プロジェクト: Maatss/Phinix
        /// <summary>
        /// Generates a <see cref="VerticalFlexContainer"/> with the offer windows and confirmation statuses.
        /// </summary>
        /// <returns><see cref="VerticalFlexContainer"/> with the offer windows and confirmation statuses</returns>
        private VerticalFlexContainer GenerateOffers()
        {
            // Create a new flex container as the main column to store everything in
            VerticalFlexContainer theAllEncompassingColumnOfOmnipotence = new VerticalFlexContainer(DEFAULT_SPACING);

            // Create a new flex container as our 'row' to store the offers and the centre column in
            HorizontalFlexContainer offerRow = new HorizontalFlexContainer(DEFAULT_SPACING);

            // Our offer
            offerRow.Add(
                new Container(
                    GenerateOurOffer(),
                    width: OFFER_WINDOW_WIDTH
                    )
                );

            // Create a new flex container as a 'column' to store the trade arrows and buttons in
            VerticalFlexContainer centreColumn = new VerticalFlexContainer(DEFAULT_SPACING);

            // Arrows
            centreColumn.Add(
                new FittedTextureWidget(
                    texture: ContentFinder <Texture2D> .Get("tradeArrows")
                    )
                );

            // Update button
            centreColumn.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_trade_updateButton".Translate(),
                        clickAction: () =>
            {
                // Do all of this in a new thread to keep the UI running smoothly
                new Thread(() =>
                {
                    try
                    {
                        // Create a new token
                        string token = Guid.NewGuid().ToString();

                        List <Thing> selectedThings = new List <Thing>();
                        lock (itemStacksLock)
                        {
                            // Collect all our things and despawn them all
                            foreach (StackedThings itemStack in itemStacks)
                            {
                                // Pop the selected things from the stack
                                Thing[] things = itemStack.PopSelected().ToArray();

                                // Despawn each spawned thing
                                foreach (Thing thing in things.Where(t => t.Spawned))
                                {
                                    thing.DeSpawn();
                                }

                                // Add them to the selected things list
                                selectedThings.AddRange(things);
                            }
                        }

                        lock (pendingItemStacksLock)
                        {
                            // Add the items to the pending dictionary
                            pendingItemStacks.Add(token, new PendingThings
                            {
                                Things    = selectedThings.ToArray(),
                                Timestamp = DateTime.UtcNow
                            });
                            Log.Message("Added items to pending");
                        }

                        // Get the items we have on offer and splice in the selected items
                        Instance.TryGetItemsOnOffer(tradeId, Instance.Uuid, out IEnumerable <ProtoThing> itemsOnOffer);
                        IEnumerable <ProtoThing> actualOffer = itemsOnOffer.Concat(selectedThings.Select(TradingThingConverter.ConvertThingFromVerse));

                        // Send an update to the server
                        Instance.UpdateTradeItems(tradeId, actualOffer, token);
                        Log.Message("Sent update");
                    }
                    catch (Exception e)
                    {
                        Log.Message(e.ToString());
                    }
                }).Start();
            }),
                    height: TRADE_BUTTON_HEIGHT
                    )
                );

            // Reset button
            centreColumn.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_trade_resetButton".Translate(),
                        clickAction: () =>
            {
                // Try to get our offer
                if (Instance.TryGetItemsOnOffer(tradeId, Instance.Uuid, out IEnumerable <ProtoThing> protoThings))
                {
                    // Convert and drop our items in pods
                    Instance.DropPods(protoThings.Select(TradingThingConverter.ConvertThingFromProto));
                }
                else
                {
                    // Report a failure
                    Instance.Log(new LogEventArgs("Failed to get our offer when resetting trade! Cannot spawn back items!", LogLevel.ERROR));
                }

                // Reset all selected counts to zero
                foreach (StackedThings itemStack in itemStacks)
                {
                    itemStack.Selected = 0;
                }

                // Update trade items
                Instance.UpdateTradeItems(tradeId, new ProtoThing[0]);
            }
                        ),
                    height: TRADE_BUTTON_HEIGHT
                    )
                );

            // Cancel button
            centreColumn.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_trade_cancelButton".Translate(),
                        clickAction: () => Instance.CancelTrade(tradeId)
                        ),
                    height: TRADE_BUTTON_HEIGHT
                    )
                );

            // Add the centre column to the row
            offerRow.Add(centreColumn);

            // Their offer
            offerRow.Add(
                new Container(
                    GenerateTheirOffer(),
                    width: OFFER_WINDOW_WIDTH
                    )
                );

            // Add the offer row to the main column
            theAllEncompassingColumnOfOmnipotence.Add(offerRow);

            // Create a new row to hold the confirmation checkboxes in
            HorizontalFlexContainer offerAcceptanceRow = new HorizontalFlexContainer(DEFAULT_SPACING);

            // Check if the backend has updated before we let the user change their offer checkbox
            if (Instance.TryGetPartyAccepted(tradeId, Instance.Uuid, out bool accepted) && tradeAccepted != accepted)
            {
                // Update the GUI's status to match the backend
                tradeAccepted = accepted;
            }

            // Our confirmation
            // TODO: Ellipsise display name length if it's going to spill over
            offerAcceptanceRow.Add(
                new Container(
                    new CheckboxLabeledWidget(
                        label: ("Phinix_trade_confirmOurTradeCheckbox" + (tradeAccepted ? "Checked" : "Unchecked")).Translate(), // Janky-looking easter egg, just for you
                        isChecked: tradeAccepted,
                        onChange: (newCheckState) =>
            {
                tradeAccepted = newCheckState;
                Instance.UpdateTradeStatus(tradeId, tradeAccepted);
            }
                        ),
                    width: OFFER_WINDOW_WIDTH
                    )
                );

            // Spacer
            offerAcceptanceRow.Add(
                new SpacerWidget()
                );

            // Their confirmation
            // TODO: Ellipsise display name length if it's going to spill over
            Instance.TryGetOtherPartyAccepted(tradeId, out bool otherPartyAccepted);
            offerAcceptanceRow.Add(
                new Container(
                    new CheckboxLabeledWidget(
                        label: ("Phinix_trade_confirmTheirTradeCheckbox" + (otherPartyAccepted ? "Checked" : "Unchecked")).Translate(TextHelper.StripRichText(GetOtherPartyDisplayName())), // Jankier-looking easter egg, just for you
                        isChecked: otherPartyAccepted,
                        onChange: null
                        ),
                    width: OFFER_WINDOW_WIDTH
                    )
                );

            // Add the offer acceptance row to the main column
            theAllEncompassingColumnOfOmnipotence.Add(
                new Container(
                    offerAcceptanceRow,
                    height: SORT_HEIGHT
                    )
                );

            // Return the generated main column
            return(theAllEncompassingColumnOfOmnipotence);
        }
コード例 #9
0
        /// <summary>
        /// Generates a <see cref="VerticalFlexContainer"/> the chat window consisting of a message list, message entry box, and a send button.
        /// </summary>
        private VerticalFlexContainer GenerateChat()
        {
            // Create a flex container to hold the column elements
            VerticalFlexContainer column = new VerticalFlexContainer(DEFAULT_SPACING);

            // Chat message area
            if (Instance.Online)
            {
                column.Add(
                    GenerateMessages()
                    );
            }
            else
            {
                column.Add(
                    new PlaceholderWidget(
                        text: "Phinix_chat_pleaseLogInPlaceholder".Translate()
                        )
                    );
            }

            // Create a flex container to hold the text field and button
            HorizontalFlexContainer messageEntryFlexContainer = new HorizontalFlexContainer();

            // Message entry field
            messageEntryFlexContainer.Add(
                new TextFieldWidget(
                    text: message,
                    onChange: newMessage => message = newMessage
                    )
                );

            // Send button
            messageEntryFlexContainer.Add(
                new WidthContainer(
                    new ButtonWidget(
                        label: "Phinix_chat_sendButton".Translate(),
                        clickAction: () =>
            {
                // Send the message
                if (!string.IsNullOrEmpty(message) && Instance.Online)
                {
                    // TODO: Make chat message 'sent' callback to remove message, preventing removal of lengthy messages for nothing and causing frustration
                    Instance.SendMessage(message);

                    message        = "";
                    scrollToBottom = true;
                }
            }
                        ),
                    width: CHAT_SEND_BUTTON_WIDTH
                    )
                );

            // Add the flex container to the column
            column.Add(
                new HeightContainer(
                    messageEntryFlexContainer,
                    height: CHAT_TEXTBOX_HEIGHT
                    )
                );

            // Return the generated column
            return(column);
        }
コード例 #10
0
        /// <inheritdoc />
        public override void Draw(Rect container)
        {
            // Background
            if (alternateBackground)
            {
                Widgets.DrawHighlight(container);
            }

            // Create a row to hold everything
            HorizontalFlexContainer row = new HorizontalFlexContainer(DEFAULT_SPACING);

            // Icon
            row.Add(
                new VerticalPaddedContainer(
                    new ThingIconWidget(
                        thing: itemStack.Things.First(),
                        scale: 0.9f
                        ),
                    ICON_WIDTH
                    )
                );

            // Item name
            row.Add(
                new TextWidget(
                    text: itemStack.Things.First().LabelCapNoCount,
                    anchor: TextAnchor.MiddleLeft,
                    wrap: true
                    )
                );

            if (interactive)
            {
                // -100 button
                row.Add(
                    new Container(
                        new ButtonWidget(
                            label: "-100",
                            clickAction: () =>
                {
                    if ((itemStack.Selected -= 100) < itemStack.Count)
                    {
                        itemStack.Selected = 0;
                    }
                }
                            ),
                        width: BUTTON_WIDTH
                        )
                    );

                // -10 button
                row.Add(
                    new Container(
                        new ButtonWidget(
                            label: "-10",
                            clickAction: () =>
                {
                    if ((itemStack.Selected -= 10) < itemStack.Count)
                    {
                        itemStack.Selected = 0;
                    }
                }
                            ),
                        width: BUTTON_WIDTH
                        )
                    );

                // -1 button
                row.Add(
                    new Container(
                        new ButtonWidget(
                            label: "-1",
                            clickAction: () =>
                {
                    if ((itemStack.Selected -= 1) < itemStack.Count)
                    {
                        itemStack.Selected = 0;
                    }
                }
                            ),
                        width: BUTTON_WIDTH
                        )
                    );

                // Count text field
                row.Add(
                    new Container(
                        new TextFieldWidget(
                            initialText: itemStack.Selected.ToString(),
                            onChange: (countText) =>
                {
                    if (int.TryParse(countText, out int result))
                    {
                        if (result < 0)
                        {
                            itemStack.Selected = 0;
                        }
                        else if (result > itemStack.Count)
                        {
                            itemStack.Selected = itemStack.Count;
                        }
                        else
                        {
                            itemStack.Selected = result;
                        }
                    }
                    else
                    {
                        itemStack.Selected = 0;
                    }
                }
                            ),
                        width: COUNT_FIELD_WIDTH
                        )
                    );

                // Available count
                row.Add(
                    new Container(
                        new TextWidget(
                            text: "/ " + itemStack.Count,
                            anchor: TextAnchor.MiddleLeft
                            ),
                        width: AVAILABLE_COUNT_WIDTH
                        )
                    );

                // +1 button
                row.Add(
                    new Container(
                        new ButtonWidget(
                            label: "+1",
                            clickAction: () =>
                {
                    if ((itemStack.Selected += 1) > itemStack.Count)
                    {
                        itemStack.Selected = itemStack.Count;
                    }
                }
                            ),
                        width: BUTTON_WIDTH
                        )
                    );

                // +10 button
                row.Add(
                    new Container(
                        new ButtonWidget(
                            label: "+10",
                            clickAction: () =>
                {
                    if ((itemStack.Selected += 10) > itemStack.Count)
                    {
                        itemStack.Selected = itemStack.Count;
                    }
                }
                            ),
                        width: BUTTON_WIDTH
                        )
                    );

                // +100 button
                row.Add(
                    new Container(
                        new ButtonWidget(
                            label: "+100",
                            clickAction: () =>
                {
                    if ((itemStack.Selected += 100) > itemStack.Count)
                    {
                        itemStack.Selected = itemStack.Count;
                    }
                }
                            ),
                        width: BUTTON_WIDTH
                        )
                    );
            }
            else
            {
                // Item count
                row.Add(
                    new Container(
                        new TextWidget(
                            text: itemStack.Count.ToString(),
                            anchor: TextAnchor.MiddleRight
                            ),
                        width: COUNT_FIELD_WIDTH
                        )
                    );
            }

            // Add some padding to keep off the edge
            row.Add(new SpacerWidget(RIGHT_PADDING));

            // Get a copy of the number of selected items
            int oldSelectedCount = itemStack.Selected;

            // Draw the row
            row.Draw(container);

            // Check if the number of selected items has changed
            if (itemStack.Selected != oldSelectedCount)
            {
                // Invoke the selected items changed callback
                onSelectedChanged?.Invoke(itemStack.Selected);
            }
        }
コード例 #11
0
        /// <summary>
        /// Generates an editable server address, editable server port, and connect button.
        /// </summary>
        /// <returns><see cref="HorizontalFlexContainer"/> containing an editable server address, editable server port, and connect button</returns>
        private HorizontalFlexContainer GenerateDisconnectedServerDetails()
        {
            // Create a flex container as our 'row' to store elements in
            HorizontalFlexContainer row = new HorizontalFlexContainer();

            // Address label
            row.Add(
                new Container(
                    new TextWidget(
                        text: "Phinix_settings_addressLabel".Translate(),
                        anchor: TextAnchor.MiddleLeft
                        ),
                    width: SERVER_ADDRESS_LABEL_WIDTH
                    )
                );

            // Server address box
            row.Add(
                new TextFieldWidget(
                    text: serverAddress,
                    onChange: newAddress => serverAddress = newAddress
                    )
                );

            // Port label
            row.Add(
                new Container(
                    new TextWidget(
                        text: "Phinix_settings_portLabel".Translate(),
                        anchor: TextAnchor.MiddleLeft
                        ),
                    width: SERVER_PORT_LABEL_WIDTH
                    )
                );

            // Server port box
            row.Add(
                new Container(
                    new TextFieldWidget(
                        text: serverPortString,
                        onChange: newPortString =>
            {
                if (new Regex("(^[0-9]{0,5}$)").IsMatch(newPortString))
                {
                    serverPortString = newPortString;
                }
            }
                        ),
                    width: SERVER_PORT_BOX_WIDTH
                    )
                );

            // Connect button
            row.Add(
                new Container(
                    new ButtonWidget(
                        label: "Phinix_settings_connectButton".Translate(),
                        clickAction: () =>
            {
                // Save the connection details to the client settings
                Client.Instance.ServerAddress = serverAddress;
                Client.Instance.ServerPort    = int.Parse(serverPortString);

                // Run this on another thread otherwise the UI will lock up.
                new Thread(() => {
                    Client.Instance.Connect(serverAddress, int.Parse(serverPortString));             // Assume the port was safely validated by the regex
                }).Start();
            }
                        ),
                    width: CONNECT_BUTTON_WIDTH
                    )
                );

            // Return the generated row
            return(row);
        }