How do I create a working terminal GUI?

Hey everyone!

I was wondering how to create a working terminal GUI? If you don’t know what I mean I mean something like the Windows 11 Command Prompt. I have been trying to figure it out for the past few days, and haven’t found anything. As always, if you need clarification on anything, I will be happy to provide you with some. I have looked for terminal GUIs on the DevForums and Reddit, and did some Google searches on them and found them, but they were not open source.

Edit: I am not looking for admin scripts, I am trying to just create a simple terminal from scratch. Basically all I need is when text is written and you hit enter, text (the output) appears under the written text. After the output, you are able to write text underneath it, and the whole process repeats.

Thank you all in advance,
Robyn

5 Likes

There is an open source resource that acts as an in-experience command line that you could use either as a reference point or just use the resource itself. See: Cmdr.

2 Likes

Not what I am looking for. I need something a little moree simple.

1 Like

Ideally you should clarify this in the original thread itself so that others don’t spend time trying to help you only for you to later outline some sort of specific requirements or semantics. This is exactly what you’re looking for which is a command line, but it doesn’t fit your needs either for the sake of using it or using it as reference/research material (for whatever reason you may have on that, despite the fact that it’s designed to be extensible and a fit for all cases).

There are various admin scripts out there as well that have mini command lines to reference. The basic gist is consuming some text input from the client and translating that into a command. I would recommend doing some basic research on this, making some attempts or sharing what you’ve already tried that supposedly turned up no results as you point out in the thread.

See the edited post. Sorry about that!

1 Like

If I understood your post correctly, I’d suggest using a TextBox on the client, firing the text along a RemoteEvent to the server, and if it is Lua code, you could use loadstring() to turn the string into code, and then you could call it in a pcall(), and output any errors it returns.

Warning: enabling loadstring() makes your game more vulnerable to exploits.

I am not trying to create admin commands. Sorry if that wasn’t clear.

I wasn’t suggesting admin commands - I didn’t mention them at all. I simply suggested a TextBox to enter the commands and then process them on the server. You could detect certain commands (not Lua code) using string.find() and connect them to seperate functions, if you are talking about things like that.

1 Like

Wasn’t what I was looking for, but that actually helps. Thank you!

1 Like

Do you mean something like Command Prompt?

Yes, I stated that in the original post.

I wrote a whole topic tutorial as a post, please “bear” with me.


I was working on a terminal for a group before, I believe it’s something simple to do. The process of making the terminal can depend on whether you want the terminal to respond and be executed in the client’s environment, the server’s environment, or maybe even both. Before you do read this, though, you should research these:

  • TextBox.ReturnPressedFromOnScreenKeyboard—There’s no documentation on this one, but it’s basically an event that fires when you press enter whilst focused on a text box.

Setting-Up

The client part of the scripting would consist of you having a UI that consists of two major parts:

  1. The OutputText/InputText (TextLabel) for the past inputs and any output text, would ideally be above the player’s input TextBox.

  2. The InputLine (TextBox) for them to input the command, no-brainer element.

  3. The Window/Container (ScrollingFrame) to store all of these UI elements together.

The rest is completely up to you. If you need a reference, I just used the command prompt from windows to best demonstrate the interface. (1 = Blue, 2 = Red, 3 = The Container/Window)
image

Note: 1 and 2 in your set-up process is for visual purposes and to make a reference. You will be creating these via script, and not by hand.

Hierarchy

The first two elements would be parented to the ContainerFrame. This is where you’ll have the text be aligned to create the effect of text being stacked and sent into the terminal. (The inputted commands and the output of those commands) To make that effect, you can simply add an UIListLayout within the container with these main properties:
image

SortOrder would be primarily used for having the earliest and latest lines be shown in a descendant order. In your terminal module/script that would be handling the UI, you should have a counter as to what text line they’re currently on. Outputs would also count as a line as well.

To better see what I mean, set the TextContainer and InputLine's layout order 2 and 1 respectively, like so:

Even a better example of how this would look when it’s done correctly can be described in this image.

The settings for the first two elements is really just a matter

Now, with that out of the way, we can actually start scripting it.

Coding The UI Terminal

Note: This code should be taken as a reference for sake of learning purposes.

Within your script, you should connect to the ReturnPressedFromOnScreenKeyboard event for the InputLine. This is so that you can detect when the player is ready to submit their command. Primarily, so that you can replace their InputLine with an InputText. Any output text would also be created afterward. Then, all you got to do is make the InputLine visible again, and set the LayoutOrder to the new current line.

Here’s a code for demonstration:

-- Like I said before, you would take note of their current line in the script
-- for the ContainerFrame's SortOrder.

InputLine.ReturnPressedFromOnScreenKeyboard:Connect(function()
	local InputText = Instance.new("TextLabel")
	InputText.Text = InputLine.Text
	InputText.TextWrapped = true
	InputText.Size = UDim2.fromScale(1, InputLine.TextBounds.Y / ContainerFrame.AbsoluteSize.Y)
	InputText.Parent = ContainerFrame
	InputText.LayoutOrder = CurrentLine
	InputText.BackgroundTransparency = 1
	InputText.Font = InputLine.Font

	InputLine.Visible = false -- hide the textbox for now

	CurrentLine += 1

	-- Just for sake of example, we're going to emulate the ouput text
	task.wait(2)

	local OutputText = Instance.new("TextLabel")
	OutputText.Text = string.format("Your inputted text is shown below: \n\n%s", InputText.Text)
	OutputText.TextWrapped = true
	OutputText.Size = UDim2.fromScale(1, OutputText.TextBounds.Y / ContainerFrame.AbsoluteSize.Y)
	OutputText.LayoutOrder = CurrentLine
	OutputText.BackgroundTransparency = 1
	OutputText.Font = InputText.Font
	OutputText.Parent = ContainerFrame

	CurrentLine += 1
	-- show the InputLine again.
	InputLine.Visible = true
	InputLine.LayoutOrder = CurrentLine
end)

And that’s the main visual part of the terminal down. If you want to add the different cursor to when they’re typing like the terminal’s cursor, then you would detect the change of the input text and have a frame to represent the cursor get adjusted to its position. You can also use it to prevent the player from removing the directory part of the text (e.g., C:\Users\edozune\Desktop>) In this example, for sake of simplicity, I won’t do that.

Coding the Terminals Back-end

We’ve coded the front-end of the terminal, but now we need to code its core functionality. I suggest you use a module for this part. If that’s too advanced for you, you can just use another script. For the sake of “perfect” coding and simplicity, I will be using a module.

The terminal has a simple process that you will replicate. The client inputs the text and their texting ability gets locked. The terminal reads the text, tries to convert it into a command, and executes said command with its possibly passed parameters. Finally, it gives the client the ability to type again. So, we’ll be making functions to do exactly this process. We’ve already done the first part of the process, (by hiding InputLine after they entered) so we’ll be doing the other four parts of the process.

  1. Processing the Text

There needs to be a command that will process the text to figure out what command the player is trying to input. How I would go around doing it is by using an array of commands to iterate from.

local Commands = {
	"help",
	"myping",
	"getplayers",
	"random [min] [max]",
}

local Terminal = {}

function Terminal:ProcessCommand(text)
	local splittedText = text:split(" ") -- we're splitting the text so that we can get the command and parameters passed to that command separated
	local input = splittedText[1]
	local parameters = table.remove(splittedText, 1)

	for _, command in Commands do
		if command == input then
			-- execute command function
		end
	end
end

return Terminal

What we would also need is a function to create any output text that the command might want to send to the player. We can use that same block of code from the InputLine event, just would have to change it to use text that was passed in the function’s parameter.

function Terminal:CreateOuputText(text)
	local OutputText = Instance.new("TextLabel")
	OutputText.Text = text
	OutputText.TextWrapped = true
	OutputText.Size = UDim2.fromScale(1, OutputText.TextBounds.Y / ContainerFrame.AbsoluteSize.Y)
	OutputText.LayoutOrder = CurrentLine
	OutputText.BackgroundTransparency = 1
	OutputText.Font = InputText.Font
	OutputText.Parent = ContainerFrame
end

function Terminal:ProcessCommand(text)
	local splittedText = text:split(" ")
	local input = splittedText[1]
	local parameters = table.remove(splittedText, 1)

	for _, command in Commands do
		if command == input then
			-- execute command
		end
	end

	-- decided to also create this little code incase the terminal couldn't find the right command
	self:CreateOuputText(string.string.format("'%s' is not recognized as an internal or external command.", command))
end
  1. Running The Command’s Function

You would need a table or module to store each of the command’s functions when it finds a match. Within the ProcessCommand function, when command == input, you simply just load the command function from the CommandFunctions and pass the parameters as a tuple.

function Terminal:ProcessCommand(text)
	local splittedText = text:split(" ")
	local input = splittedText[1]
	local parameters = table.remove(splittedText, 1)

	for _, command in Commands do
		if command == input then
			-- execute command
			CommandFunctions[command](unpack(parameters))
		end
	end

	self:CreateOuputText(string.string.format("'%s' is not recognized as an internal or external command.", command))
end

And now you’re practically done with the module. Here’s the full reference module after these changes:

local Commands = {
	"help",
	"myping",
	"getplayers",
	"random [min] [max]",
}

local CommandFunctions = require(path.to.CommandFunctions)

-- If you plan on having some commands be executed on the server,
-- you would instead put the commands in a module as a table
-- for both the server and client to read. You would also need
-- to adjust your functions to detect whether the command is
-- supposed to be ran on the server or not.

local Terminal = {}

function Terminal:CreateOuputText(text)
	local OutputText = Instance.new("TextLabel")
	OutputText.Text = text
	OutputText.TextWrapped = true
	OutputText.Size = UDim2.fromScale(1, OutputText.TextBounds.Y / ContainerFrame.AbsoluteSize.Y)
	OutputText.LayoutOrder = CurrentLine
	OutputText.BackgroundTransparency = 1
	OutputText.Font = InputText.Font
	OutputText.Parent = ContainerFrame
end

function Terminal:ProcessCommand(text)
	local splittedText = text:split(" ") -- we're splitting the text so that we can get the command and parameters passed to that command separated
	local input = splittedText[1]
	local parameters = table.remove(splittedText, 1)

	for _, command in Commands do
		if command == input then
			-- execute command
			CommandFunctions[command](unpack(parameters))
		end
	end

	-- decided to also create this little code incase the terminal couldn't find the right command
	self:CreateOuputText(string.string.format("'%s' is not recognized as an internal or external command.", command))
end

return Terminal

Now, we go back to the script that handles the InputLine text and require the Terminal module. After that, we just plug the ProcessCommand into the enter function and it should be finished.

InputLine.ReturnPressedFromOnScreenKeyboard:Connect(function()
	local InputText = Instance.new("TextLabel")
	InputText.Text = InputLine.Text
	InputText.TextWrapped = true
	InputText.Size = UDim2.fromScale(1, InputLine.TextBounds.Y / ContainerFrame.AbsoluteSize.Y)
	InputText.Parent = ContainerFrame
	InputText.LayoutOrder = CurrentLine
	InputText.BackgroundTransparency = 1
	InputText.Font = InputLine.Font
	InputLine.Visible = false

	CurrentLine += 1

	Terminal:ProcessCommand(InputText.Text)
	CurrentLine += 1
	
	InputLine.Visible = true
	InputLine.LayoutOrder = CurrentLine
end)

Hopefully this helps. If it doesn’t you got me and the community.

If you want some help with figuring out how to have commands get executed from the server to the client, you can PM me and I’ll be happy to help.

7 Likes

WOW!! Thank you very much!!! I do have one question (as of now, there will probably be more). I have the visual part of the terminal done, but whenever I enter too many commands, the terminal just breaks and the textbox just goes out side the ContainerFrame. Is there any fix for this?

Oh yeah, I totally forgot to mention that you should tick the ClipDescendents property of the ContainerFrame so that the textboxes will be contained within the frame, and not bleed out the scrolling frame. You do this anyway if you’re using a ScrollingFrame.
image

And as I should’ve mentioned, you should have AutoSize be set to Y, and the CanvasSize to be set to {0, 0, 0, 0}, so that the canvas can be scrolled automatically without you having to code that in yourself.

Oh, and don’t forget to mark my post as the solution if it’s what you wanted, and it helped get you the results that you desire, to help anyone else that might have the same wonders as you do. Any other questions you might have, like I said, feel free to PM me or ask the community. Happy developing!

2 Likes

Thank you, I thought I had marked it as a solution, but apparently not… No wonder you have 67 solutions, that was the best post I have ever seen.

I will probably have more questions off and on so thank you!

2 Likes

So with further testing, the textbox just went offscreen this time (If you need a video of what I am trying to explain to you, I will provide you with one.)

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.