Skip to content

Commit

Permalink
GenericDocument is now editable.
Browse files Browse the repository at this point in the history
For a demo, type `loadscript example_GenericDoc_editor.as` to the ingame console. Then follow instructions on the menu bar.
  • Loading branch information
ohlidalp committed Oct 18, 2023
1 parent 6d0a2d3 commit 07ed962
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 2 deletions.
194 changes: 194 additions & 0 deletions resources/scripts/example_GenericDoc_editor.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Prototype truck editor, Oct 2023
// (sorry about the indentation mess, I scribbled this in 'script_editor.as')
// ===================================================

class RigEditor
{
// ---- variables -----

GenericDocumentClass@ g_displayed_document = null;
string g_displayed_doc_filename;
int m_hovered_token_pos = -1;
int m_focused_token_pos = -1;

// ---- functions ----

void drawWindow()
{

string caption = "RigEditor (" + g_displayed_doc_filename + ")";
int flags = ImGuiWindowFlags_MenuBar;
ImGui::Begin(caption, /*open:*/true, flags);
if (ImGui::BeginMenuBar())
{
this.drawDocumentControls(); // <- menubar
ImGui::EndMenuBar();
}
if (@g_displayed_document != null)
{
vector2 size = ImGui::GetWindowSize() - vector2(25, 200);
ImGui::BeginChild("docBody", size);
this.drawDocumentBody();
ImGui::EndChild();
}

ImGui::End();

}

// helper of `drawWindow()`
void drawDocumentControls()
{

if (@g_displayed_document != null)
{
ImGui::TextDisabled("Document loaded OK.");
}
else
{
BeamClass@ actor = game.getCurrentTruck();
if (@actor != null)
{
// Actor name and "View document" button
ImGui::PushID("actor");
ImGui::AlignTextToFramePadding();
ImGui::Text("You are driving " + actor.getTruckName());
ImGui::SameLine();
if (@g_displayed_document == null)
{
if (ImGui::Button("View document"))
{
GenericDocumentClass@ doc = GenericDocumentClass();
int flags = GENERIC_DOCUMENT_OPTION_ALLOW_NAKED_STRINGS
| GENERIC_DOCUMENT_OPTION_ALLOW_SLASH_COMMENTS
| GENERIC_DOCUMENT_OPTION_FIRST_LINE_IS_TITLE
| GENERIC_DOCUMENT_OPTION_ALLOW_SEPARATOR_COLON
| GENERIC_DOCUMENT_OPTION_PARENTHESES_CAPTURE_SPACES;
if (doc.loadFromResource(actor.getTruckFileName(), actor.getTruckFileResourceGroup(), flags))
{
@g_displayed_document = @doc;
g_displayed_doc_filename = actor.getTruckFileName();
}
}
}
else
{
if (ImGui::Button("Close document"))
{
@g_displayed_document = null;
g_displayed_doc_filename = "";
}
}
ImGui::PopID(); //"actor"
}
else
{
ImGui::Text("You are on foot");
}
}
}

void drawDocumentBody()
{
ImGui::PushID("docBody");
bool hover_found = false;

GenericDocContextClass reader(g_displayed_document);
while (!reader.endOfFile())
{


switch (reader.tokenType())
{
// These tokens are always at start of line
case TOKEN_TYPE_KEYWORD:
ImGui::TextColored(tokenColor(reader), reader.getTokKeyword());
break;
case TOKEN_TYPE_COMMENT:
ImGui::TextColored(tokenColor(reader), ";" + reader.getTokComment());
break;

// Linebreak is implicit in DearIMGUI, no action needed
case TOKEN_TYPE_LINEBREAK:
ImGui::SameLine(); ImGui::Text(""); // hack to fix highlight of last token on line.
break;

// Other tokens come anywhere - delimiting logic is needed
default:
if (reader.getPos() != 0 && reader.tokenType(-1) != TOKEN_TYPE_LINEBREAK)
{
ImGui::SameLine();
// string delimiter = (reader.tokenType(-1) == TOKEN_TYPE_KEYWORD) ? " " : ", ";
// ImGui::Text(delimiter);
// ImGui::SameLine();
}

switch (reader.tokenType())
{
case TOKEN_TYPE_STRING:
ImGui::TextColored(tokenColor(reader), "\"" + reader.getTokString() + "\"");
break;
case TOKEN_TYPE_NUMBER:
ImGui::TextColored(tokenColor(reader), "" + reader.getTokFloat());
break;
case TOKEN_TYPE_BOOL:
ImGui::TextColored(tokenColor(reader), ""+reader.getTokBool());
break;
}
}

if (ImGui::IsItemHovered()) {
m_hovered_token_pos = reader.getPos() ;
hover_found = true;
}

if (ImGui::IsItemClicked(0))
{
m_focused_token_pos = reader.getPos();
}

reader.moveNext();

}

if (!hover_found)
{
m_hovered_token_pos = -1;
}

ImGui::PopID(); // "docBody"
}

color tokenColor(GenericDocContextClass@ reader)
{
if (m_focused_token_pos > -1 && reader.getPos() == uint(m_focused_token_pos))
{
return color(0.9f, 0.1f, 0.1f, 1.f);
}

if (m_hovered_token_pos > -1 && reader.getPos() == uint(m_hovered_token_pos))
{
return color(0.1f, 1.f, 0.1f, 1.f);
}

switch (reader.tokenType())
{
case TOKEN_TYPE_KEYWORD: return color(1.f, 1.f, 0.f, 1.f);
case TOKEN_TYPE_COMMENT: return color(0.5f, 0.5f, 0.5f, 1.f);
case TOKEN_TYPE_STRING: return color(0.f, 1.f, 1.f, 1.f);
case TOKEN_TYPE_NUMBER: return color(0.9, 0.9, 0.9, 1.f);
case TOKEN_TYPE_BOOL: return color(1.f, 0.f, 1.f, 1.f);
} // end switch
return color(0.9, 0.9, 0.9, 1.f);
} // end tokenColor()
}

RigEditor rigEditor;

// `frameStep()` runs every frame; `dt` is delta time in seconds.
void frameStep(float dt)
{
rigEditor.drawWindow();
}


13 changes: 13 additions & 0 deletions source/main/scripting/bindings/GenericFileFormatAngelscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ void RoR::RegisterGenericFileFormat(asIScriptEngine* engine)


// class GenericDocContext
// (Please maintain the same order as in 'GenericFileFormat.h' and 'doc/*/GenericDocContextClass.h')
GenericDocContext::RegisterRefCountingObject(engine, "GenericDocContextClass");
GenericDocContextPtr::RegisterRefCountingObjectPtr(engine, "GenericDocContextClassPtr", "GenericDocContextClass");
engine->RegisterObjectBehaviour("GenericDocContextClass", asBEHAVE_FACTORY, "GenericDocContextClass@+ f(GenericDocumentClassPtr @)", asFUNCTION(GenericDocContextFactory), asCALL_CDECL);
Expand All @@ -116,4 +117,16 @@ void RoR::RegisterGenericFileFormat(asIScriptEngine* engine)
engine->RegisterObjectMethod("GenericDocContextClass", "bool isTokBool(int offset = 0)", asMETHOD(GenericDocContext, isTokBool), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool isTokKeyword(int offset = 0)", asMETHOD(GenericDocContext, isTokKeyword), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool isTokComment(int offset = 0)", asMETHOD(GenericDocContext, isTokComment), asCALL_THISCALL);

// > Editing functions:
engine->RegisterObjectMethod("GenericDocContextClass", "bool insertToken(int offset = 0)", asMETHOD(GenericDocContext, insertToken), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool eraseToken(int offset = 0)", asMETHOD(GenericDocContext, eraseToken), asCALL_THISCALL);

engine->RegisterObjectMethod("GenericDocContextClass", "bool setTokString(int offset, const string &in)", asMETHOD(GenericDocContext, setTokString), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool setTokFloat(int offset, float)", asMETHOD(GenericDocContext, setTokFloat), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool setTokBool(int offset, bool)", asMETHOD(GenericDocContext, setTokBool), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool setTokKeyword(int offset, const string &in)", asMETHOD(GenericDocContext, setTokKeyword), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool setTokComment(int offset, const string &in)", asMETHOD(GenericDocContext, setTokComment), asCALL_THISCALL);
engine->RegisterObjectMethod("GenericDocContextClass", "bool setTokLineBreak(int offset)", asMETHOD(GenericDocContext, setTokBool), asCALL_THISCALL);

}
46 changes: 46 additions & 0 deletions source/main/utils/GenericFileFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1059,3 +1059,49 @@ int GenericDocContext::countLineArgs()
count++;
return count;
}

// -----------------
// Editing functions

bool GenericDocContext::insertToken(int offset)
{
if (endOfFile(offset))
return false;

doc->tokens.insert(doc->tokens.begin() + token_pos + offset, { TokenType::NONE, 0.f });
return true;
}

bool GenericDocContext::eraseToken(int offset)
{
if (endOfFile(offset))
return false;

// Just erase the token.
// We don't care about garbage in `string_pool` - the strings are usually just 1-6 characters long anyway.

doc->tokens.erase(doc->tokens.begin() + token_pos + offset);
return true;
}

bool GenericDocContext::setStringData(int offset, TokenType type, const std::string& data)
{
if (endOfFile(offset))
return false;

// Insert the string at the end of the string_pool
// We don't care about order - updating string offsets in tokens would be complicated and unlikely helpful.

doc->tokens[token_pos + offset] = { type, (float)doc->string_pool.size() };
std::copy(data.begin(), data.end(), std::back_inserter(doc->string_pool));
return true;
}

bool GenericDocContext::setFloatData(int offset, TokenType type, float data)
{
if (endOfFile(offset))
return false;

doc->tokens[token_pos + offset] = { type, data };
return true;
}
25 changes: 23 additions & 2 deletions source/main/utils/GenericFileFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,18 @@ typedef RefCountingObjectPtr<GenericDocument> GenericDocumentPtr;

struct GenericDocContext: public RefCountingObject<GenericDocContext>
{
GenericDocContext(GenericDocumentPtr d) : doc(d) {}
GenericDocContext(GenericDocumentPtr d) : doc(d)
{
ROR_ASSERT(doc != nullptr);
if (doc == nullptr && AngelScript::asGetActiveContext() != nullptr)
{
AngelScript::asGetActiveContext()->SetException("Cannot create GenericDocContextClass from null GenericDocument!");
}
}
virtual ~GenericDocContext() {};

GenericDocumentPtr doc;
uint32_t token_pos = 0;
uint32_t line_num = 0;

// PLEASE maintain the same order as in 'bindings/GenericFileFormatAngelscript.cpp' and 'doc/*/GenericDocContextClass.h'

Expand All @@ -115,9 +121,24 @@ struct GenericDocContext: public RefCountingObject<GenericDocContext>
bool isTokKeyword(int offset = 0) const { return tokenType(offset) == TokenType::KEYWORD; }
bool isTokComment(int offset = 0) const { return tokenType(offset) == TokenType::COMMENT; }

// Editing functions:

bool insertToken(int offset = 0); //!< Inserts `TokenType::NONE`; @return false if offset is beyond EOF
bool eraseToken(int offset = 0); //!< @return false if offset is beyond EOF

bool setTokString(int offset, const std::string& str) { return setStringData(offset, TokenType::STRING, str); }
bool setTokFloat(int offset, float val) { return setFloatData(offset, TokenType::NUMBER, val); }
bool setTokBool(int offset, bool val) { return setFloatData(offset, TokenType::BOOL, val); }
bool setTokKeyword(int offset, const std::string& str) { return setStringData(offset, TokenType::KEYWORD, str); }
bool setTokComment(int offset, const std::string& str) { return setStringData(offset, TokenType::COMMENT, str); }
bool setTokLineBreak(int offset) { return setFloatData(offset, TokenType::LINEBREAK, 0.f); }

// Not exported to script:

const char* getStringData(int offset = 0) const { return !endOfFile(offset) ? (doc->string_pool.data() + (uint32_t)doc->tokens[token_pos + offset].data) : nullptr; }
float getFloatData(int offset = 0) const { return !endOfFile(offset) ? doc->tokens[token_pos + offset].data : 0.f; }
bool setStringData(int offset, TokenType type, const std::string& data); //!< @return false if offset is beyond EOF
bool setFloatData(int offset, TokenType type, float data); //!< @return false if offset is beyond EOF
};

typedef RefCountingObjectPtr<GenericDocContext> GenericDocContextPtr;
Expand Down

0 comments on commit 07ed962

Please sign in to comment.