public SettingsWindow() { doCloseX = true; doCloseButton = false; doWindowBackground = true; // Create a flex container to hold our settings contents = new VerticalFlexContainer(DEFAULT_SPACING); // Server details (address and [dis]connect button) container contents.Add( new ConditionalContainer( childIfTrue: GenerateConnectedServerDetails(), childIfFalse: GenerateDisconnectedServerDetails(), condition: () => Client.Instance.Connected ) ); // Display name and preview contents.Add( new ConditionalContainer( childIfTrue: GenerateEditableDisplayName(), childIfFalse: new BlankWidget(), condition: () => Client.Instance.Online ) ); }
/// <summary> /// Generates a <see cref="VerticalFlexContainer"/> containing their offer. /// </summary> /// <returns><see cref="VerticalFlexContainer"/> containing their offer</returns> private VerticalFlexContainer GenerateTheirOffer() { // Create a flex container as our 'column' to store elements in VerticalFlexContainer column = new VerticalFlexContainer(0f); // Title column.Add( new Container( new TextWidget( text: "Phinix_trade_theirOfferLabel".Translate(), anchor: TextAnchor.MiddleCenter ), height: OFFER_WINDOW_TITLE_HEIGHT ) ); // Draw their items lock (theirOfferCacheLock) { column.Add( GenerateItemList( itemStacks: theirOfferCache, scrollPos: theirOfferScrollPos, scrollUpdate: newScrollPos => theirOfferScrollPos = newScrollPos ) ); } // Return the generated flex container return(column); }
/// <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="VerticalScrollContainer"/> containing an item list within the given container. /// </summary> /// <param name="itemStacks">Item stacks to draw in the list</param> /// <param name="scrollPos">List scroll position</param> /// <param name="scrollUpdate">Action invoked with the scroll position of the item list when it is drawn</param> /// <param name="interactive">Whether the item counts should be modifiable by the user</param> private VerticalScrollContainer GenerateItemList(IEnumerable <StackedThings> itemStacks, Vector2 scrollPos, Action <Vector2> scrollUpdate, bool interactive = false) { // Create a new flex container as our 'column' to hold each element VerticalFlexContainer column = new VerticalFlexContainer(0f); // Set up a list to hold our item stack rows int iterations = 0; foreach (StackedThings itemStack in itemStacks) { // Create an ItemStackRow from this item ItemStackRow row = new ItemStackRow( itemStack: itemStack, interactive: interactive, alternateBackground: iterations++ % 2 != 0, // Be careful of the positioning of ++ here, this should increment /after/ the operation onSelectedChanged: _ => // We don't need the value, so we can just assign it to _ { //Client.Instance.UpdateTradeItems(tradeId, this.itemStacks.SelectMany(stack => stack.GetSelectedThingsAsProto())); } ); // Contain the row within a minimum-height container MinimumContainer container = new MinimumContainer( row, minHeight: OFFER_WINDOW_ROW_HEIGHT ); // Add it to the row list column.Add(container); } // Return the flex container wrapped in a scroll container return(new VerticalScrollContainer(column, scrollPos, scrollUpdate)); }
/// <summary> /// Generates a <see cref="VerticalScrollContainer"/> containing a series of available trades. /// </summary> /// <returns></returns> private Displayable GenerateTradeRows() { // Make sure we are online and have active trades before attempting to draw them if (!Instance.Online) { return(new PlaceholderWidget("Phinix_chat_pleaseLogInPlaceholder".Translate())); } else if (Instance.GetTrades().Count() == 0) { return(new PlaceholderWidget("Phinix_trade_noActiveTradesPlaceholder".Translate())); } // Create a column to store everything in VerticalFlexContainer column = new VerticalFlexContainer(DEFAULT_SPACING); // Get TradeRows for each trade and add them to the column string[] tradeIds = Instance.GetTrades(); for (int i = 0; i < tradeIds.Length; i++) { column.Add( new TradeRow( tradeId: tradeIds[i], drawAlternateBackground: i % 2 != 0 ) ); } // Return the generated column wrapped in a scroll container return(new VerticalScrollContainer(column, activeTradesScroll, newScrollPos => activeTradesScroll = newScrollPos)); }
public override void Draw(Rect inRect) { // Try refresh the trade list, passing until the next frame if it is occupied if (Monitor.TryEnter(tradeRowsLock)) { if (tradesUpdated) { // Clear out the inner container and repopulate it with the new trades tradeRowsFlexContainer.Contents.Clear(); for (int i = 0; i < tradeRows.Count; i++) { // Update the alternate background state tradeRows[i].DrawAlternateBackground = i % 2 != 0; tradeRowsFlexContainer.Add(tradeRows[i]); } // Reset the updated flag tradesUpdated = false; } Monitor.Exit(tradeRowsLock); } // Draw the list constructedLayout.Draw(inRect); }
/// <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 a settings button, user search box, and a user list. /// </summary> private VerticalFlexContainer GenerateRightColumn() { // Create a flex container to hold the column elements VerticalFlexContainer column = new VerticalFlexContainer(); // Settings button column.Add( new Container( new ButtonWidget( label: "Phinix_chat_settingsButton".Translate(), clickAction: () => Find.WindowStack.Add(new SettingsWindow()) ), height: SETTINGS_BUTTON_HEIGHT ) ); // User search box column.Add( new Container( new TextFieldWidget( initialText: userSearch, onChange: (newText) => { userSearch = newText; userList.Update(); } ), height: USER_SEARCH_HEIGHT ) ); // User list column.Add( new ConditionalContainer( childIfTrue: userList, childIfFalse: new PlaceholderWidget(), condition: () => Instance.Online ) ); // Return the generated column return(column); }
public override void DoWindowContents(Rect inRect) { doCloseX = true; doCloseButton = false; doWindowBackground = true; // Create a flex container to hold our settings VerticalFlexContainer flexContainer = new VerticalFlexContainer(DEFAULT_SPACING); // Server details (address and [dis]connect button) container if (Client.Instance.Connected) { flexContainer.Add(GenerateConnectedServerDetails()); } else { flexContainer.Add(GenerateDisconnectedServerDetails()); } // Display name and preview if (Client.Instance.Online) { flexContainer.Add(GenerateEditableDisplayName()); flexContainer.Add(GenerateNamePreview()); } ; // Calculate height and constrain the container so we have even row heights with fluid contents float contentHeight = 0f; foreach (Displayable item in flexContainer.Contents) { contentHeight += item.IsFluidHeight ? ROW_HEIGHT : item.CalcHeight(inRect.width); } contentHeight += (flexContainer.Contents.Count - 1) * DEFAULT_SPACING; HeightContainer heightContainer = new HeightContainer(flexContainer, contentHeight); // Draw the container with 5f padding at the top to avoid clipping with the close button // flexContainer.Draw(inRect.BottomPartPixels(inRect.height - 5f)); heightContainer.Draw(inRect.BottomPartPixels(inRect.height - 5f)); }
/// <summary> /// Adds each logged in user to a scrollable container. /// </summary> /// <returns>A <see cref="VerticalScrollContainer"/> containing the user list</returns> private VerticalScrollContainer GenerateUserList() { // Create a flex container to hold the users VerticalFlexContainer userListFlexContainer = new VerticalFlexContainer(); // Add each logged in user to the flex container foreach (string uuid in Instance.GetUserUuids(true)) { // Try to get the display name of the user if (!Instance.TryGetDisplayName(uuid, out string displayName)) { displayName = "???"; } // Skip the user if they don't contain the search text if (!string.IsNullOrEmpty(userSearch) && !displayName.ToLower().Contains(userSearch.ToLower())) { continue; } // Strip name formatting if the user wishes not to see it if (!Instance.ShowNameFormatting) { displayName = TextHelper.StripRichText(displayName); } userListFlexContainer.Add( new ButtonWidget( label: displayName, clickAction: () => DrawUserContextMenu(uuid, displayName), drawBackground: false ) ); } // Wrap the flex container in a scroll container VerticalScrollContainer verticalScrollContainer = new VerticalScrollContainer(userListFlexContainer, userListScroll, newScrollPos => userListScroll = newScrollPos); // Return the scroll container return(verticalScrollContainer); }
/// <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); }