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()); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); }
/// <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); } }
/// <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); }