Skip to content

Commit

Permalink
Added half functioning gradient UI
Browse files Browse the repository at this point in the history
  • Loading branch information
SIsilicon committed Jan 26, 2024
1 parent 4b0371f commit 0c25541
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 96 deletions.
31 changes: 17 additions & 14 deletions src/library/@types/classes/uiFormBuilder.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,66 @@ type UIFormName = `$${string}`
type UIAction<T extends {}, S> = (ctx: MenuContext<T>, player: Player) => S
type DynamicElem<T extends {}, S> = S | UIAction<T, S>

interface BaseInput<T extends {}> {
interface BaseInput<T extends {}, S> {
name: DynamicElem<T, string>
type: string
default?: DynamicElem<T, S>
}

interface Slider<T extends {}> extends BaseInput<T> {
interface Slider<T extends {}> extends BaseInput<T, number> {
type: "slider"
min: DynamicElem<T, number>
max: DynamicElem<T, number>
step?: DynamicElem<T, number>
default?: DynamicElem<T, number>
}

interface Dropdown<T extends {}> extends BaseInput<T> {
interface Dropdown<T extends {}> extends BaseInput<T, number> {
type: "dropdown"
options: DynamicElem<T, string[]>,
default?: DynamicElem<T, number>
}

interface TextField<T extends {}> extends BaseInput<T> {
interface TextField<T extends {}> extends BaseInput<T, string> {
type: "textField"
placeholder: DynamicElem<T, string>,
default?: DynamicElem<T, string>
}

interface Toggle<T extends {}> extends BaseInput<T> {
interface Toggle<T extends {}> extends BaseInput<T, boolean> {
type: "toggle"
default?: DynamicElem<T, boolean>
}

type Input<T extends {}> = Slider<T> | Dropdown<T> | TextField<T> | Toggle<T>
type SubmitAction<T extends {}> = (ctx: MenuContext<T>, player: Player, input: {[key: UIFormName]: string|number|boolean}) => void

interface Button<T extends {}> {
text: DynamicElem<T, string>
icon?: DynamicElem<T, string>
action: UIAction<T, void>
}

interface ActionButton<T extends {}> extends Button<T> {
icon?: DynamicElem<T, string>
visible?: DynamicElem<T, boolean>
}

interface BaseForm<T extends {}> {
/** The title of the UI form */
title: DynamicElem<T, string>,
title: DynamicElem<T, string>
/** Action to perform when the user exits or cancels the form */
cancel?: UIAction<T, void>
}

/** A form with a message and one or two options */
/** A form with a message and two options */
interface MessageForm<T extends {}> extends BaseForm<T> {
message: DynamicElem<T, string>
button1: Button<T>
button2?: Button<T>
button2: Button<T>
}

/** A form with an array of buttons to interact with */
interface ActionForm<T extends {}> extends BaseForm<T> {
/** Text that appears above the array of buttons */
message?: DynamicElem<T, string>
/** The array of buttons to interact with */
buttons: DynamicElem<T, Button<T>[]>
buttons: DynamicElem<T, ActionButton<T>[]>
}

interface ModalForm<T extends {}> extends BaseForm<T> {
Expand All @@ -80,6 +81,8 @@ interface MenuContext<T extends {}> {
setData<S extends keyof T>(key: S, value: T[S]): void
goto(menu: UIFormName): void
returnto(menu: UIFormName): void
back(): void
confirm(title: string, message: string, yes: UIAction<T, void>, no?: UIAction<T, void>): void
}

export { Form, FormData, UIAction, DynamicElem, MessageForm, ActionForm, SubmitAction, ModalForm, UIFormName, MenuContext };
125 changes: 64 additions & 61 deletions src/library/classes/uiFormBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,14 @@ abstract class UIForm<T extends {}> {
public exit(player: Player, ctx: MenuContextType<T>) { /**/ }

protected handleCancel(response: FormResponse, player: Player, ctx: MenuContext<T>) {
if (response.canceled) {
if (response.cancelationReason == FormCancelationReason.UserBusy) {
setTickTimeout(() => this.enter(player, ctx));
} else {
ctx.goto(null);
this.cancelAction?.(ctx, player);
}
return true;
if (!response.canceled) return false;
if (response.cancelationReason == FormCancelationReason.UserBusy) {
setTickTimeout(() => this.enter(player, ctx));
} else {
ctx.goto(undefined);
this.cancelAction?.(ctx, player);
}
return false;
return true;
}

protected buildFormData(player: Player, ctx: MenuContext<T>) {
Expand All @@ -39,11 +37,7 @@ abstract class UIForm<T extends {}> {
}

protected resolve<S>(element: DynamicElem<T, S>, player: Player, ctx: MenuContext<T>) {
if (element instanceof Function) {
return element(ctx, player);
} else {
return element;
}
return element instanceof Function ? element(ctx, player) : element;
}
}

Expand All @@ -53,31 +47,27 @@ class MessageUIForm<T extends {}> extends UIForm<T> {

constructor(form: MessageForm<T>) {
super(form);
this.action1 = form.button1.action;
this.action2 = form.button2?.action;
this.action1 = form.button2.action;
this.action2 = form.button1.action;
}

protected build(form: MessageForm<T>, resEl: <S>(elem: DynamicElem<T, S>) => S) {
const formData = new MessageFormData();
formData.title(resEl(form.title));
formData.body(resEl(form.message));
formData.button1(resEl(form.button1.text));
if ("button2" in form) {
formData.button2(resEl(form.button2.text));
}
formData.button1(resEl(form.button2.text));
formData.button2(resEl(form.button1.text));
return formData;
}

enter(player: Player, ctx: MenuContext<T>) {
this.buildFormData(player, ctx).show(player).then((response: MessageFormResponse) => {
if (this.handleCancel(response, player, ctx)) {
return;
}
ctx.goto(null);
if (this.handleCancel(response, player, ctx)) return;
ctx.goto(undefined);
if (response.selection == 0) {
this.action1(ctx, player);
} else if (response.selection == 1) {
(this.action2 ?? this.action1)(ctx, player);
this.action2(ctx, player);
}
});
}
Expand All @@ -91,8 +81,10 @@ class ActionUIForm<T extends {}> extends UIForm<T> {
const formData = new ActionFormData();
formData.title(resEl(form.title));

if (form.message) {
formData.body(resEl(form.message));
if (form.message) formData.body(resEl(form.message));
if (resEl((ctx) => (<MenuContext<T>>ctx).canGoBack())) {
formData.button("<< Back");
this.actions.push((ctx) => ctx.back());
}
for (const button of resEl(form.buttons)) {
formData.button(resEl(button.text), resEl(button.icon));
Expand All @@ -105,10 +97,8 @@ class ActionUIForm<T extends {}> extends UIForm<T> {
const form = this.buildFormData(player, ctx);
const actions = this.actions;
form.show(player).then((response: ActionFormResponse) => {
if (this.handleCancel(response, player, ctx)) {
return;
}
ctx.goto(null);
if (this.handleCancel(response, player, ctx)) return;
ctx.goto(undefined);
actions[response.selection]?.(ctx, player);
});
}
Expand Down Expand Up @@ -161,23 +151,20 @@ class ModalUIForm<T extends {}> extends UIForm<T> {
const form = this.buildFormData(player, ctx);
const inputNames = this.inputNames;
form.show(player).then((response: ModalFormResponse) => {
if (this.handleCancel(response, player, ctx)) {
return;
}
if (this.handleCancel(response, player, ctx)) return;
const inputs: {[key: string]: string|number|boolean} = {};
for (const i in response.formValues) {
inputs[inputNames[i]] = response.formValues[i];
}
ctx.goto(null);
ctx.goto(undefined);
this.submit(ctx, player, inputs);
});
}
}

class MenuContext<T extends {}> implements MenuContextType<T> {
private stack: string[] = [];
private stack: `$${string}`[] = [];
private data: T = {} as T;
private currentForm: UIForm<T>;

constructor(private player: Player) {}

Expand All @@ -189,31 +176,53 @@ class MenuContext<T extends {}> implements MenuContextType<T> {
this.data[key] = value;
}

goto(menu: UIFormName) {
if (menu) {
this.stack.push(menu);
}
if (this.stack.length >= 64) {
throw Error("UI Stack overflow!");
goto(menu?: UIFormName) {
if (menu && this.stack[this.stack.length - 1] === "$___confirmMenu___") {
throw Error("Can't go to another form from a confirmation menu!");
}
this.currentForm = UIForms.goto(menu, this.player, this);
this._goto(menu);
}

back() {
this.stack.pop();
this._goto(this.stack.pop());
}

returnto(menu: UIFormName) {
let popped: string;
let popped: string | undefined;
// eslint-disable-next-line no-cond-assign
while (popped = this.stack.pop()) {
if (popped == menu) {
this.goto(menu);
while ((popped = this.stack.pop())) {
if (popped === menu) {
this._goto(menu);
return;
}
}
this.goto(null);
this._goto(undefined);
}

confirm(title: string, message: string, yes: UIAction<T, void>, no?: UIAction<T, void>) {
this.stack.push("$___confirmMenu___");
const form = new MessageUIForm({
title,
message,
button1: { text: "No", action: no ?? ((ctx) => ctx.back()) },
button2: { text: "Yes", action: yes },
});
form.enter(this.player, this);
}

canGoBack() {
return this.stack.length > 1;
}

private _goto(menu?: UIFormName) {
if (menu && menu !== this.stack[this.stack.length - 1]) this.stack.push(menu);
if (this.stack.length >= 64) throw Error("UI Stack overflow!");
UIForms.goto(menu, this.player, this);
}
}

class UIFormBuilder {

private forms = new Map<UIFormName, UIForm<{}>>();
private active = new Map<Player, UIForm<{}>>();

Expand Down Expand Up @@ -284,17 +293,11 @@ class UIFormBuilder {
* @returns Whether the UI, or any at all is being displayed.
*/
displayingUI(player: Player, ui?: UIFormName) {
if (this.active.has(player)) {
if (ui) {
const form = this.active.get(player);
for (const registered of this.forms.values()) {
if (registered == form) {
return true;
}
}
return false;
}
return true;
if (!this.active.has(player)) return false;
if (!ui) return true;
const form = this.active.get(player);
for (const registered of this.forms.values()) {
if (registered == form) return true;
}
return false;
}
Expand Down
4 changes: 4 additions & 0 deletions src/server/modules/directions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,8 @@ export class Cardinal implements CustomArgType {
return cardinal;
}
}

getDirectionLetter() {
return this.direction;
}
}
22 changes: 15 additions & 7 deletions src/server/modules/hotbar_ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class HotbarUIForm<T extends {}> {
}

class HotbarContext<T extends {}> implements MenuContext<T> {
private stack: string[] = [];
private stack: UIFormName[] = [];
private data: T = {} as T;
private currentForm: HotbarUIForm<T>;

Expand Down Expand Up @@ -146,17 +146,25 @@ class HotbarContext<T extends {}> implements MenuContext<T> {
}
}

back() {
this.stack.pop();
this.goto(this.stack.pop());
}

returnto(menu: UIFormName) {
let popped: string;
let popped: string | undefined;
// eslint-disable-next-line no-cond-assign
while (popped = this.stack.pop()) {
if (popped == menu) {
while ((popped = this.stack.pop())) {
if (popped === menu) {
this.goto(menu);
return;
}
}
this.goto(null);
this.base?.returnto(menu);
this.goto(undefined);
}

confirm() {
throw "confirm() is not implemented in hotbar UI";
}
}

Expand Down Expand Up @@ -198,7 +206,7 @@ class HotbarUIBuilder {
* @param player The player to display the UI form to
* @param ctx The context to be passed to the UI form
*/
goto(name: UIFormName, player: Player, ctx: MenuContext<{}>) {
goto(name: UIFormName, player: Player, ctx: MenuContext<unknown>) {
if (!(ctx instanceof HotbarContext)) {
ctx = new HotbarContext(player, ctx);
}
Expand Down
6 changes: 5 additions & 1 deletion src/server/modules/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ export class Pattern implements CustomArgType {
}

addBlock(permutation: BlockPermutation) {
if (this.block == null) {
if (!this.block) {
this.block = new ChainPattern(null);
} else if (!(this.block instanceof ChainPattern)) {
const old = this.block;
this.block = new ChainPattern(null);
this.block.nodes.push(old);
}

const block = blockPermutation2ParsedBlock(permutation);
Expand Down
Loading

0 comments on commit 0c25541

Please sign in to comment.