mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-05 14:32:52 +00:00
REMOVES TGUI (#49330)
* Removes tgui * Make tgui script executable again * Set version of packages to 2.0.0 * Update copypasta docs to match the current best practice * Rebuild tgui * Update .github/CONTRIBUTING.md Co-Authored-By: BadSS13Player <58376109+BadSS13Player@users.noreply.github.com> * Remove holy bible by Arcane * rebuild Co-authored-by: BadSS13Player <58376109+BadSS13Player@users.noreply.github.com> Co-authored-by: Rob Bailey <actioninja@gmail.com>
This commit is contained in:
242
tgui/docs/tutorial-and-examples.md
Normal file
242
tgui/docs/tutorial-and-examples.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Tutorial and Examples
|
||||
|
||||
## Main concepts
|
||||
|
||||
Basic tgui backend code consists of the following vars and procs:
|
||||
|
||||
```
|
||||
ui_interact(mob/user, ui_key, datum/tgui/ui, force_open,
|
||||
datum/tgui/master_ui, datum/ui_state/state)
|
||||
ui_data(mob/user)
|
||||
ui_act(action, params)
|
||||
```
|
||||
|
||||
- `src_object` - The atom, which UI corresponds to in the game world.
|
||||
- `ui_interact` - The proc where you will handle a request to open an
|
||||
interface. Typically, you would update an existing UI (if it exists),
|
||||
or set up a new instance of UI by calling the `SStgui` subsystem.
|
||||
- `ui_data` - In this proc you munges whatever complex data your `src_object`
|
||||
has into an associative list, which will then be sent to UI as a JSON string.
|
||||
- `ui_act` - This proc receives user actions and reacts to them by changing
|
||||
the state of the game.
|
||||
- `ui_state` (set in `ui_interact`) - This var dictates under what conditions
|
||||
a UI may be interacted with. This may be the standard checks that check if
|
||||
you are in range and conscious, or more.
|
||||
|
||||
Once backend is complete, you create an new interface component on the
|
||||
frontend, which will receive this JSON data and render it on screen.
|
||||
|
||||
States are easy to write and extend, and what make tgui interactions so
|
||||
powerful. Because states can be overridden from other procs, you can build
|
||||
powerful interactions for embedded objects or remote access.
|
||||
|
||||
## Using It
|
||||
|
||||
### Backend
|
||||
|
||||
Let's start with a very basic hello world.
|
||||
|
||||
```dm
|
||||
/obj/machinery/my_machine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "my_machine", name, 300, 300, master_ui, state)
|
||||
ui.open()
|
||||
```
|
||||
|
||||
This is the proc that defines our interface. There's a bit going on here, so
|
||||
let's break it down. First, we override the ui_interact proc on our object. This
|
||||
will be called by `interact` for you, which is in turn called by `attack_hand`
|
||||
(or `attack_self` for items). `ui_interact` is also called to update a UI (hence
|
||||
the `try_update_ui`), so we accept an existing UI to update. The `state` is a
|
||||
default argument so that a caller can overload it with named arguments
|
||||
(`ui_interact(state = overloaded_state)`) if needed.
|
||||
|
||||
Inside the `if(!ui)` block (which means we are creating a new UI), we choose our
|
||||
template, title, and size; we can also set various options like `style` (for
|
||||
themes), or autoupdate. These options will be elaborated on later (as will
|
||||
`ui_state`s).
|
||||
|
||||
After `ui_interact`, we need to define `ui_data`. This just returns a list of
|
||||
data for our object to use. Let's imagine our object has a few vars:
|
||||
|
||||
```dm
|
||||
/obj/machinery/my_machine/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["health"] = health
|
||||
data["color"] = color
|
||||
|
||||
return data
|
||||
```
|
||||
|
||||
The `ui_data` proc is what people often find the hardest about tgui, but its
|
||||
really quite simple! You just need to represent your object as numbers, strings,
|
||||
and lists, instead of atoms and datums.
|
||||
|
||||
Finally, the `ui_act` proc is called by the interface whenever the user used an
|
||||
input. The input's `action` and `params` are passed to the proc.
|
||||
|
||||
```dm
|
||||
/obj/machinery/my_machine/ui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
if(action == "change_color")
|
||||
var/new_color = params["color"]
|
||||
if(!(color in allowed_coors))
|
||||
return FALSE
|
||||
color = new_color
|
||||
return TRUE
|
||||
update_icon()
|
||||
```
|
||||
|
||||
The `..()` (parent call) is very important here, as it is how we check that the
|
||||
user is allowed to use this interface (to avoid so-called href exploits). It is
|
||||
also very important to clamp and sanitize all input here. Always assume the user
|
||||
is attempting to exploit the game.
|
||||
|
||||
Also note the use of `. = TRUE` (or `FALSE`), which is used to notify the UI
|
||||
that this input caused an update. This is especially important for UIs that do
|
||||
not auto-update, as otherwise the user will never see their change.
|
||||
|
||||
### Frontend
|
||||
|
||||
Finally, you have to make a UI component. This is also a source of
|
||||
confusion for many new users. If you got some basic javascript and HTML
|
||||
knowledge, that should ease the learning process, although we recommend
|
||||
getting yourself introduced to
|
||||
[React and JSX](https://reactjs.org/docs/introducing-jsx.html).
|
||||
|
||||
A component is not a regular HTML. A component is a pure function, which
|
||||
accepts a `props` object (it contains properties passed to a component),
|
||||
and outputs an HTML-like structure consisting of regular HTML elements and
|
||||
other UI components.
|
||||
|
||||
Interface component will always receive 1 prop which is called `state`.
|
||||
This object contains a few special values:
|
||||
|
||||
- `config` is always the same and is part of core tgui
|
||||
(it will be explained later),
|
||||
- `data` is the data returned from `ui_data`
|
||||
|
||||
```jsx
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, LabeledList } from '../components';
|
||||
|
||||
export const SampleInterface = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
return (
|
||||
<Section title="Health status">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Health">
|
||||
{data.health}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Color">
|
||||
{data.color}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
This syntax can be very confusing at first, but it is very important to
|
||||
realize that this is just a natural extension of javascript. Here's a few
|
||||
examples of this syntax:
|
||||
|
||||
Return a different element based on a condition:
|
||||
|
||||
```jsx
|
||||
if (condition) {
|
||||
return <Foo />;
|
||||
}
|
||||
return <Bar />;
|
||||
```
|
||||
|
||||
Conditionally render a element inside of another element:
|
||||
|
||||
```jsx
|
||||
<Box>
|
||||
{showProgress && (
|
||||
<ProgressBar value={progress} />
|
||||
)}
|
||||
</Box>
|
||||
```
|
||||
|
||||
Looping over the array to make an element for each item:
|
||||
|
||||
```jsx
|
||||
<LabeledList>
|
||||
{items.map(item => (
|
||||
<LabeledList.Item key={item.id} label={item.label}>
|
||||
{item.content}
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
```
|
||||
|
||||
### Routing table
|
||||
|
||||
Once you finished creating your interface, you must add a route entry to
|
||||
the large `ROUTES` object, otherwise tgui won't know when and how to render
|
||||
your interface. Key of this `ROUTES` object corresponds to the interface
|
||||
name you use in DM code.
|
||||
|
||||
```js
|
||||
import { SampleInterface } from './interfaces/SampleInterface';
|
||||
|
||||
const ROUTES = {
|
||||
sample_interface: {
|
||||
component: () => SampleInterface,
|
||||
scrollable: true,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Copypasta
|
||||
|
||||
We all do it, even the best of us. If you just want to make a tgui **fast**,
|
||||
here's what you need (note that you'll probably be forced to clean your shit up
|
||||
upon code review):
|
||||
|
||||
```dm
|
||||
/obj/copypasta/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/ui_state/state = default_state) // Remember to use the appropriate state.
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "copypasta", name, 300, 300, master_ui, state)
|
||||
ui.open()
|
||||
|
||||
/obj/copypasta/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["var"] = var
|
||||
return data
|
||||
|
||||
/obj/copypasta/ui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
if(action == "copypasta")
|
||||
var/newvar = params["var"]
|
||||
// A demo of proper input sanitation.
|
||||
var = CLAMP(newvar, min_val, max_val)
|
||||
return TRUE
|
||||
update_icon() // Not applicable to all objects.
|
||||
```
|
||||
|
||||
And the template:
|
||||
|
||||
```jsx
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, LabeledList } from '../components';
|
||||
|
||||
export const SampleInterface = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
return (
|
||||
<Section title="Section name">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Variable">
|
||||
{data.var}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
```
|
||||
Reference in New Issue
Block a user