init mod
This commit is contained in:
commit
7a088b25a9
58 files changed
+6053
No files matched your search
@@ -0,0 +1,62 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// "Log" tab: what happened, newest last — scene entries, skipped scenarios
|
||||
/// with the reason, scenario starts and ends, stops and studio edits.
|
||||
/// Skips are tinted as warnings because they are the usual answer to
|
||||
/// "nothing happened".
|
||||
/// </summary>
|
||||
public static class StudioLogPage
|
||||
{
|
||||
private const int TimeWidth = 44;
|
||||
|
||||
public static void Build(VBoxContainer content, StudioSession session)
|
||||
{
|
||||
var list = UiKit.Column(0);
|
||||
content.AddChild(list);
|
||||
|
||||
foreach (var entry in session.Log)
|
||||
{
|
||||
var plate = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
plate.AddThemeStyleboxOverride("panel", StudioStyles.LogRow(entry.IsWarning));
|
||||
|
||||
var row = UiKit.Row(UiTokens.Space3);
|
||||
plate.AddChild(row);
|
||||
|
||||
var time = StudioStyles.Text(entry.Time, UiTokens.FontSmall, UiTokens.Faint, mono: true);
|
||||
time.CustomMinimumSize = new Vector2(TimeWidth, 0);
|
||||
row.AddChild(time);
|
||||
row.AddChild(UiKit.Badge(TranslationServer.Translate(CategoryKey(entry.Category)), StudioStyles.LogColor(entry.Category)));
|
||||
|
||||
var text = UiKit.Label(entry.Text.Resolve(), UiTokens.FontBodySmall, entry.IsWarning ? UiTokens.Warning : UiTokens.Body);
|
||||
text.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||
text.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
|
||||
row.AddChild(text);
|
||||
list.AddChild(plate);
|
||||
}
|
||||
}
|
||||
|
||||
private static string CategoryKey(StudioLogCategory category)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case StudioLogCategory.Scene:
|
||||
return "studio/log_scene";
|
||||
case StudioLogCategory.Skip:
|
||||
return "studio/log_skip";
|
||||
case StudioLogCategory.Stop:
|
||||
return "studio/log_stop";
|
||||
case StudioLogCategory.End:
|
||||
return "studio/log_end";
|
||||
case StudioLogCategory.Edit:
|
||||
return "studio/log_edit";
|
||||
default:
|
||||
return "studio/log_scenario";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// "Map" tab: the walk between scenes around the player, laid out as a
|
||||
/// vertical path because the dock is narrow — where the player came from,
|
||||
/// where they are, and the exits from here with the command or choice that
|
||||
/// leads there. Exits can be taken directly (a debugging teleport); new
|
||||
/// scenes are created in the studio layer, the current scenario can be
|
||||
/// linked to any scene, and the layer can be exported as a regular mod.
|
||||
/// </summary>
|
||||
public static class StudioMapPage
|
||||
{
|
||||
public static void Build(VBoxContainer content, StudioSession session, Action<StudioActionButton> go, Action<StudioActionButton> newScene, Action<StudioActionButton> link, Action<StudioActionButton> export)
|
||||
{
|
||||
var visited = false;
|
||||
foreach (var node in session.Nodes)
|
||||
{
|
||||
if (node.State != StudioNodeState.Visited)
|
||||
continue;
|
||||
if (!visited)
|
||||
content.AddChild(UiKit.SectionTitle(TranslationServer.Translate("studio/map_from"), UiTokens.FontSmall));
|
||||
visited = true;
|
||||
content.AddChild(BuildNode(node, go));
|
||||
content.AddChild(Arrow());
|
||||
}
|
||||
|
||||
if (!visited)
|
||||
content.AddChild(UiKit.SectionTitle(TranslationServer.Translate("studio/map_here"), UiTokens.FontSmall));
|
||||
foreach (var node in session.Nodes)
|
||||
{
|
||||
if (node.State == StudioNodeState.Here)
|
||||
content.AddChild(BuildNode(node, null));
|
||||
}
|
||||
|
||||
content.AddChild(UiKit.SectionTitle(TranslationServer.Translate("studio/map_to"), UiTokens.FontSmall));
|
||||
var exits = 0;
|
||||
foreach (var node in session.Nodes)
|
||||
{
|
||||
if (node.State != StudioNodeState.Next && node.State != StudioNodeState.New)
|
||||
continue;
|
||||
content.AddChild(BuildNode(node, go));
|
||||
exits++;
|
||||
}
|
||||
if (exits == 0)
|
||||
content.AddChild(UiKit.Hint(TranslationServer.Translate("studio/map_no_exits"), UiTokens.FontBodySmall));
|
||||
|
||||
var actions = new HFlowContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
actions.AddThemeConstantOverride("h_separation", UiTokens.Space2);
|
||||
actions.AddThemeConstantOverride("v_separation", UiTokens.Space2);
|
||||
actions.AddChild(StudioActionButton.Create("studio/map_new_scene", newScene));
|
||||
if (session.HasScenario)
|
||||
actions.AddChild(StudioActionButton.Create("studio/map_link", link));
|
||||
actions.AddChild(StudioActionButton.Create("studio/map_export", export));
|
||||
content.AddChild(actions);
|
||||
}
|
||||
|
||||
private static Control BuildNode(StudioSceneNode node, Action<StudioActionButton> go)
|
||||
{
|
||||
var card = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
card.AddThemeStyleboxOverride("panel", StudioStyles.Node(node.State));
|
||||
|
||||
var row = UiKit.Row(UiTokens.Space3);
|
||||
card.AddChild(row);
|
||||
|
||||
var column = UiKit.Column(UiTokens.Space1);
|
||||
column.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
|
||||
row.AddChild(column);
|
||||
|
||||
var head = UiKit.Row(UiTokens.Space3);
|
||||
var nameColor = node.State == StudioNodeState.Here ? UiTokens.AccentBright : UiTokens.Ink;
|
||||
head.AddChild(StudioStyles.Text(node.Name, UiTokens.FontBodySmall, nameColor, mono: true));
|
||||
head.AddChild(StudioStyles.Scope(node.Scope));
|
||||
column.AddChild(head);
|
||||
|
||||
var sub = TranslationServer.Translate(StateKey(node.State));
|
||||
if (node.Via != null)
|
||||
sub += " · " + node.Via.Resolve();
|
||||
column.AddChild(StudioStyles.Text(sub, UiTokens.FontSmall, UiTokens.Faint));
|
||||
|
||||
if (node.State != StudioNodeState.Here)
|
||||
{
|
||||
var button = StudioActionButton.Create("studio/map_go", go);
|
||||
button.Payload = node.Name;
|
||||
button.SizeFlagsVertical = Control.SizeFlags.ShrinkCenter;
|
||||
row.AddChild(button);
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
private static Control Arrow()
|
||||
{
|
||||
var arrow = StudioStyles.Text("↓", UiTokens.FontBody, UiTokens.Faint);
|
||||
arrow.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
return arrow;
|
||||
}
|
||||
|
||||
private static string StateKey(StudioNodeState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case StudioNodeState.Here:
|
||||
return "studio/node_here";
|
||||
case StudioNodeState.Visited:
|
||||
return "studio/node_visited";
|
||||
case StudioNodeState.New:
|
||||
return "studio/node_new";
|
||||
default:
|
||||
return "studio/node_next";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// "Now" tab in the novice mode: the moment of the story rather than the
|
||||
/// script (<see cref="StudioMoment"/>) — what is on screen, the lines said
|
||||
/// right before it, every answer with where it leads, then what else the
|
||||
/// scene has queued. The only insert actions are the ones that make sense
|
||||
/// here: a line before the question (or after a plain line) and a new
|
||||
/// answer, which starts broken until its target is chosen. Requests carry
|
||||
/// 0-based indices into the scenario text, where the dock inserts. Ends
|
||||
/// with the same "this change affects" plate as the author mode.
|
||||
/// </summary>
|
||||
public sealed class StudioNovicePage
|
||||
{
|
||||
/// <summary>A new line is requested at the given text index.</summary>
|
||||
public event Action<int> LineRequested;
|
||||
|
||||
/// <summary>A new answer is requested at the given text index.</summary>
|
||||
public event Action<int> OptionRequested;
|
||||
|
||||
/// <summary>"Edit" on a line, question or answer.</summary>
|
||||
public event Action<StudioActionButton> EditRequested;
|
||||
|
||||
/// <summary>"Choose where" on an answer.</summary>
|
||||
public event Action<StudioActionButton> TargetRequested;
|
||||
|
||||
private StudioMoment _moment;
|
||||
|
||||
public void Build(VBoxContainer content, StudioSession session)
|
||||
{
|
||||
_moment = StudioMoment.Build(session);
|
||||
if (_moment.Say == null)
|
||||
{
|
||||
content.AddChild(UiKit.EmptyState(TranslationServer.Translate(session.HasScenario ? "studio/moment_empty" : "studio/no_scenario"), UiTokens.FontBody));
|
||||
return;
|
||||
}
|
||||
|
||||
content.AddChild(StudioStyles.Caption(TranslationServer.Translate("studio/moment_now")));
|
||||
foreach (var line in _moment.Before)
|
||||
content.AddChild(new StudioSayView(line, true, OnEdit));
|
||||
content.AddChild(new StudioSayView(_moment.Say, false, OnEdit));
|
||||
|
||||
if (_moment.IsQuestion)
|
||||
{
|
||||
content.AddChild(StudioStyles.Caption(TranslationServer.Translate("studio/moment_answers")));
|
||||
foreach (var branch in _moment.Branches)
|
||||
content.AddChild(new StudioBranchView(branch, OnEdit, OnTarget));
|
||||
}
|
||||
|
||||
var actions = UiKit.Row(UiTokens.Space2);
|
||||
actions.AddChild(InsertButton(_moment.IsQuestion ? "studio/add_line_before" : "studio/add_line_after", OnLinePressed));
|
||||
if (_moment.IsQuestion)
|
||||
actions.AddChild(InsertButton("studio/add_option", OnOptionPressed));
|
||||
content.AddChild(actions);
|
||||
|
||||
content.AddChild(StudioStyles.Caption(TranslationServer.Translate("studio/moment_next")));
|
||||
content.AddChild(BuildNext(session));
|
||||
content.AddChild(StudioNowPage.BuildScopeBanner(session));
|
||||
}
|
||||
|
||||
private static Control BuildNext(StudioSession session)
|
||||
{
|
||||
foreach (var candidate in session.Candidates)
|
||||
{
|
||||
if (candidate.Status == StudioCandidateStatus.Queued)
|
||||
return UiKit.Hint(string.Format(TranslationServer.Translate("studio/next_waiting"), candidate.Name), UiTokens.FontBodySmall);
|
||||
}
|
||||
return UiKit.Hint(TranslationServer.Translate("studio/next_none"), UiTokens.FontBodySmall);
|
||||
}
|
||||
|
||||
private static Button InsertButton(string key, Action handler)
|
||||
{
|
||||
var button = StudioStyles.SmallButton(new Button(), TranslationServer.Translate(key));
|
||||
button.Pressed += handler;
|
||||
return button;
|
||||
}
|
||||
|
||||
private void OnLinePressed()
|
||||
{
|
||||
var say = _moment.Say.Number - 1;
|
||||
LineRequested?.Invoke(_moment.IsQuestion ? say : say + 1);
|
||||
}
|
||||
|
||||
private void OnOptionPressed()
|
||||
{
|
||||
var last = _moment.Branches.Count > 0 ? _moment.Branches[^1].Line : _moment.Say;
|
||||
OptionRequested?.Invoke(last.Number);
|
||||
}
|
||||
|
||||
private void OnEdit(StudioActionButton button) => EditRequested?.Invoke(button);
|
||||
|
||||
private void OnTarget(StudioActionButton button) => TargetRequested?.Invoke(button);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// "Now" tab in the author mode: the running scenario as script with a
|
||||
/// gutter (<see cref="StudioCodeLineView"/>) and the insert bar for a line
|
||||
/// after the current one. It ends with the "this change affects" plate
|
||||
/// naming the mod that receives the edit and the mod it overrides, so
|
||||
/// nobody changes someone else's content unknowingly; the novice mode
|
||||
/// (<see cref="StudioNovicePage"/>) shows the same plate.
|
||||
/// <para/>
|
||||
/// Dragging the ▶ marker of the current line highlights the line under the
|
||||
/// mouse when the running dialog can continue from it
|
||||
/// (<see cref="StudioCodeLine.CanRunNext"/>) and on release asks for it
|
||||
/// with <see cref="NextLineRequested"/>; a drop anywhere else does nothing.
|
||||
/// <para/>
|
||||
/// One instance lives as long as the dock; <see cref="Build"/> refills the
|
||||
/// body and wires the controls to this instance's handlers.
|
||||
/// </summary>
|
||||
public sealed class StudioNowPage
|
||||
{
|
||||
/// <summary>A gutter click changed a breakpoint.</summary>
|
||||
public event Action<StudioCodeLine> BreakpointToggled;
|
||||
|
||||
/// <summary>One of the insert buttons was pressed.</summary>
|
||||
public event Action<StudioLineKind> InsertRequested;
|
||||
|
||||
/// <summary>The ▶ marker was dropped on a line the dialog can continue from.</summary>
|
||||
public event Action<StudioCodeLine> NextLineRequested;
|
||||
|
||||
private readonly List<StudioCodeLineView> _views = new();
|
||||
private StudioCodeLineView _dropTarget;
|
||||
private bool _dragging;
|
||||
|
||||
/// <summary>True while the ▶ marker is dragged: a redraw would drop the drag.</summary>
|
||||
public bool IsDragging => _dragging;
|
||||
|
||||
public void Build(VBoxContainer content, StudioSession session)
|
||||
{
|
||||
_views.Clear();
|
||||
_dropTarget = null;
|
||||
_dragging = false;
|
||||
if (!session.HasScenario)
|
||||
{
|
||||
content.AddChild(UiKit.EmptyState(TranslationServer.Translate("studio/no_scenario"), UiTokens.FontBody));
|
||||
return;
|
||||
}
|
||||
|
||||
var well = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
well.AddThemeStyleboxOverride("panel", StudioStyles.CodeWell());
|
||||
content.AddChild(well);
|
||||
|
||||
var lines = UiKit.Column(0);
|
||||
well.AddChild(lines);
|
||||
foreach (var line in session.Lines)
|
||||
{
|
||||
var view = new StudioCodeLineView(line);
|
||||
view.BreakpointToggled += OnBreakpointToggled;
|
||||
view.MarkerDragged += OnMarkerDragged;
|
||||
view.MarkerDropped += OnMarkerDropped;
|
||||
lines.AddChild(view);
|
||||
_views.Add(view);
|
||||
}
|
||||
|
||||
var at = session.InsertionTextIndexAfterCurrent();
|
||||
var caption = at <= 0 ? string.Empty : string.Format(TranslationServer.Translate("studio/after_line"), at);
|
||||
content.AddChild(BuildInsertBar(caption));
|
||||
content.AddChild(BuildScopeBanner(session));
|
||||
}
|
||||
|
||||
private Control BuildInsertBar(string caption)
|
||||
{
|
||||
var bar = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
bar.AddThemeStyleboxOverride("panel", StudioStyles.InsertBar());
|
||||
|
||||
var row = new HFlowContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
row.AddThemeConstantOverride("h_separation", UiTokens.Space2);
|
||||
row.AddThemeConstantOverride("v_separation", UiTokens.Space2);
|
||||
bar.AddChild(row);
|
||||
|
||||
if (!string.IsNullOrEmpty(caption))
|
||||
{
|
||||
var label = StudioStyles.Text(caption, UiTokens.FontSmall, UiTokens.Faint);
|
||||
label.VerticalAlignment = VerticalAlignment.Center;
|
||||
label.SizeFlagsVertical = Control.SizeFlags.ShrinkCenter;
|
||||
row.AddChild(label);
|
||||
}
|
||||
|
||||
row.AddChild(InsertButton("studio/add_line", OnAddLine));
|
||||
row.AddChild(InsertButton("studio/add_choice", OnAddChoice));
|
||||
row.AddChild(InsertButton("studio/add_jump", OnAddJump));
|
||||
return bar;
|
||||
}
|
||||
|
||||
private static Button InsertButton(string key, Action handler)
|
||||
{
|
||||
var button = StudioStyles.SmallButton(new Button(), TranslationServer.Translate(key));
|
||||
button.Pressed += handler;
|
||||
return button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline editor of the whole scenario text (the footer's "Edit text"),
|
||||
/// filling the body; the dock reads the text back when editing ends.
|
||||
/// </summary>
|
||||
public static CodeEdit BuildEditor(VBoxContainer content, string text)
|
||||
{
|
||||
var editor = new CodeEdit
|
||||
{
|
||||
Text = text ?? string.Empty,
|
||||
GuttersDrawLineNumbers = true,
|
||||
HighlightCurrentLine = true,
|
||||
WrapMode = TextEdit.LineWrappingMode.None,
|
||||
SizeFlagsHorizontal = Control.SizeFlags.ExpandFill,
|
||||
CustomMinimumSize = new Vector2(0, 560),
|
||||
SyntaxHighlighter = StudioSyntax.CreateHighlighter(),
|
||||
};
|
||||
editor.AddThemeFontOverride("font", StudioStyles.Mono);
|
||||
editor.AddThemeFontSizeOverride("font_size", UiTokens.FontBodySmall);
|
||||
content.AddChild(editor);
|
||||
return editor;
|
||||
}
|
||||
|
||||
/// <summary>"This change affects" plate: the edit scope and, when it differs, the mod it overrides.</summary>
|
||||
public static Control BuildScopeBanner(StudioSession session)
|
||||
{
|
||||
var banner = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
banner.AddThemeStyleboxOverride("panel", StudioStyles.ScopeBanner());
|
||||
|
||||
var column = UiKit.Column(UiTokens.Space2);
|
||||
banner.AddChild(column);
|
||||
|
||||
var title = StudioStyles.Text(TranslationServer.Translate("studio/affects"), UiTokens.FontTiny, UiTokens.Warning);
|
||||
title.Uppercase = true;
|
||||
column.AddChild(title);
|
||||
|
||||
var row = new HFlowContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
row.AddThemeConstantOverride("h_separation", UiTokens.Space3);
|
||||
column.AddChild(row);
|
||||
if (session.EditScope == null)
|
||||
return banner;
|
||||
row.AddChild(StudioStyles.Scope(session.EditScope));
|
||||
if (session.EditScope.ModId != session.FileScope.ModId)
|
||||
{
|
||||
row.AddChild(StudioStyles.Text(TranslationServer.Translate("studio/overrides"), UiTokens.FontSmall, UiTokens.Faint));
|
||||
row.AddChild(StudioStyles.Scope(session.FileScope));
|
||||
}
|
||||
|
||||
return banner;
|
||||
}
|
||||
|
||||
private void OnBreakpointToggled(StudioCodeLine line) => BreakpointToggled?.Invoke(line);
|
||||
|
||||
private void OnMarkerDragged(Vector2 at)
|
||||
{
|
||||
_dragging = true;
|
||||
var target = DropTargetAt(at);
|
||||
if (target == _dropTarget)
|
||||
return;
|
||||
_dropTarget?.ShowDropTarget(false);
|
||||
_dropTarget = target;
|
||||
_dropTarget?.ShowDropTarget(true);
|
||||
}
|
||||
|
||||
private void OnMarkerDropped(Vector2 at)
|
||||
{
|
||||
_dragging = false;
|
||||
_dropTarget?.ShowDropTarget(false);
|
||||
_dropTarget = null;
|
||||
var target = DropTargetAt(at);
|
||||
if (target != null)
|
||||
NextLineRequested?.Invoke(target.Line);
|
||||
}
|
||||
|
||||
/// <summary>The row under <paramref name="at"/> (canvas coordinates) the dialog can continue from, or null.</summary>
|
||||
private StudioCodeLineView DropTargetAt(Vector2 at)
|
||||
{
|
||||
foreach (var view in _views)
|
||||
{
|
||||
if (view.Line.CanRunNext && view.GetGlobalRect().HasPoint(at))
|
||||
return view;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnAddLine() => InsertRequested?.Invoke(StudioLineKind.Line);
|
||||
|
||||
private void OnAddChoice() => InsertRequested?.Invoke(StudioLineKind.Choice);
|
||||
|
||||
private void OnAddJump() => InsertRequested?.Invoke(StudioLineKind.Jump);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// "Scene" tab: every scenario the last composition of the current scene
|
||||
/// considered, in start order, with the owning mod, priority, header
|
||||
/// condition and its result, and the status with the reason in words. This
|
||||
/// is the answer to "why did my scenario not start": a false condition, a
|
||||
/// one-run scenario already completed, or another mod winning the same
|
||||
/// scenario name. Any candidate can be started now; a completed one can be
|
||||
/// made fresh again; a new scenario for the scene goes to the studio layer.
|
||||
/// </summary>
|
||||
public static class StudioScenePage
|
||||
{
|
||||
public static void Build(VBoxContainer content, StudioSession session, Action<StudioActionButton> run, Action<StudioActionButton> reset, Action<StudioActionButton> create)
|
||||
{
|
||||
var hint = UiKit.Hint(TranslationServer.Translate("studio/cand_hint"));
|
||||
hint.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||
content.AddChild(hint);
|
||||
|
||||
if (session.Candidates.Count == 0)
|
||||
content.AddChild(UiKit.EmptyState(TranslationServer.Translate("studio/cand_none"), UiTokens.FontBody));
|
||||
foreach (var candidate in session.Candidates)
|
||||
content.AddChild(BuildCard(candidate, run, reset));
|
||||
|
||||
var add = StudioActionButton.Create("studio/new_scenario", create);
|
||||
add.Payload = session.SceneName;
|
||||
add.SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin;
|
||||
content.AddChild(add);
|
||||
}
|
||||
|
||||
private static Control BuildCard(StudioCandidate candidate, Action<StudioActionButton> run, Action<StudioActionButton> reset)
|
||||
{
|
||||
var running = candidate.Status == StudioCandidateStatus.Running;
|
||||
var card = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
card.AddThemeStyleboxOverride("panel", StudioStyles.Candidate(running));
|
||||
|
||||
var column = UiKit.Column(UiTokens.Space2);
|
||||
card.AddChild(column);
|
||||
|
||||
var head = UiKit.Row(UiTokens.Space3);
|
||||
head.AddChild(StudioStyles.Text(candidate.Name, UiTokens.FontBody, UiTokens.InkBright, mono: true));
|
||||
head.AddChild(UiKit.Spacer());
|
||||
head.AddChild(UiKit.Badge(TranslationServer.Translate(StatusKey(candidate.Status)), StudioStyles.StatusColor(candidate.Status)));
|
||||
column.AddChild(head);
|
||||
|
||||
var meta = new HFlowContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
meta.AddThemeConstantOverride("h_separation", UiTokens.Space3);
|
||||
meta.AddChild(StudioStyles.Scope(candidate.Scope));
|
||||
meta.AddChild(StudioStyles.Text(string.Format(TranslationServer.Translate("studio/priority"), candidate.Priority), UiTokens.FontSmall, UiTokens.Muted, mono: true));
|
||||
var condition = candidate.Condition ?? TranslationServer.Translate("studio/no_condition");
|
||||
var mark = candidate.ConditionOk == null ? "" : candidate.ConditionOk.Value ? "✓ " : "✗ ";
|
||||
var conditionColor = candidate.ConditionOk == false ? UiTokens.DangerText : candidate.ConditionOk == true ? UiTokens.SuccessText : UiTokens.Muted;
|
||||
meta.AddChild(StudioStyles.Text(mark + condition, UiTokens.FontSmall, conditionColor, mono: true));
|
||||
if (candidate.IsOneRun)
|
||||
meta.AddChild(StudioStyles.Text(TranslationServer.Translate("studio/one_run"), UiTokens.FontSmall, UiTokens.Muted));
|
||||
column.AddChild(meta);
|
||||
|
||||
var reason = UiKit.Label(candidate.Reason?.Resolve() ?? string.Empty, UiTokens.FontSmall, UiTokens.Muted);
|
||||
reason.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||
column.AddChild(reason);
|
||||
|
||||
if (running || candidate.Status == StudioCandidateStatus.Shadowed)
|
||||
return card;
|
||||
|
||||
var actions = UiKit.Row(UiTokens.Space2);
|
||||
var start = StudioActionButton.Create("studio/run_now", run);
|
||||
start.Payload = candidate.Name;
|
||||
actions.AddChild(start);
|
||||
if (candidate.Status == StudioCandidateStatus.Completed)
|
||||
{
|
||||
var fresh = StudioActionButton.Create("studio/reset_completed", reset);
|
||||
fresh.Payload = candidate.Name;
|
||||
actions.AddChild(fresh);
|
||||
}
|
||||
column.AddChild(actions);
|
||||
return card;
|
||||
}
|
||||
|
||||
private static string StatusKey(StudioCandidateStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case StudioCandidateStatus.Running:
|
||||
return "studio/cand_running";
|
||||
case StudioCandidateStatus.Queued:
|
||||
return "studio/cand_queued";
|
||||
case StudioCandidateStatus.Skipped:
|
||||
return "studio/cand_skipped";
|
||||
case StudioCandidateStatus.Completed:
|
||||
return "studio/cand_completed";
|
||||
case StudioCandidateStatus.Failed:
|
||||
return "studio/cand_failed";
|
||||
default:
|
||||
return "studio/cand_shadowed";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace Cthangover.Mods.ScenarioStudio
|
||||
{
|
||||
using System;
|
||||
using Cthangover.Core.UI.Theming;
|
||||
using Cthangover.Mods.Interface.Theming;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// "State" tab: game state grouped by owner — time, dialog variables of the
|
||||
/// running dialog, quests the current scenario refers to, inventory and the
|
||||
/// one-run completion history. Variables, item counts and quest status are
|
||||
/// editable (Enter writes them into the game), because changing one is the
|
||||
/// quickest way to test another branch of a scenario.
|
||||
/// </summary>
|
||||
public static class StudioStatePage
|
||||
{
|
||||
public static void Build(VBoxContainer content, StudioSession session, Action<StudioStateEntry, string> submitted)
|
||||
{
|
||||
var hint = UiKit.Hint(TranslationServer.Translate("studio/state_hint"));
|
||||
hint.AutowrapMode = TextServer.AutowrapMode.WordSmart;
|
||||
content.AddChild(hint);
|
||||
|
||||
string group = null;
|
||||
VBoxContainer rows = null;
|
||||
foreach (var entry in session.State)
|
||||
{
|
||||
if (entry.Group.Key != group)
|
||||
{
|
||||
group = entry.Group.Key;
|
||||
content.AddChild(UiKit.SectionTitle(entry.Group.Resolve(), UiTokens.FontSmall));
|
||||
var well = new PanelContainer { MouseFilter = Control.MouseFilterEnum.Ignore };
|
||||
well.AddThemeStyleboxOverride("panel", StudioStyles.Well());
|
||||
content.AddChild(well);
|
||||
rows = UiKit.Column(UiTokens.Space2);
|
||||
well.AddChild(rows);
|
||||
}
|
||||
|
||||
var row = new StudioStateRow(entry);
|
||||
row.Submitted += submitted;
|
||||
rows.AddChild(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user