Skip to content

Commit

Permalink
Rewrote the C#-JS binding (#17)
Browse files Browse the repository at this point in the history
- Proper view model with versioning of changes on C# side
- jQuery based test Web app works reliably to show blocks and their properties
- Modification of block properties work (test Web app is unusable with frequently updated blocks, like active rotors)
- Missing: interacted block ID, grids, named groups
- Slows down as the browser is open, collects some threads/garbage, needs to be profiled
  • Loading branch information
viktor-ferenczi authored Oct 31, 2021
1 parent 3773f66 commit ed72884
Show file tree
Hide file tree
Showing 23 changed files with 953 additions and 446 deletions.
5 changes: 3 additions & 2 deletions EnhancedUI/Content/ControlPanel.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
<link rel="stylesheet" href="common.css"/>
<link rel="stylesheet" href="ControlPanel.css"/>
<script src="jquery-3.6.0.min.js"></script>
<script src="common.js"></script>
<script src="ControlPanel.js"></script>
</head>
<body>

<div class="fullscreen">
<div id="blocks"></div>
</div>

<script src="common.js"></script>
<script src="ControlPanel.js"></script>

</body>
</html>
241 changes: 143 additions & 98 deletions EnhancedUI/Content/ControlPanel.js
Original file line number Diff line number Diff line change
@@ -1,164 +1,209 @@
var blockStates = null;
let displayedVersion = 0;
let rendering = false;

// Invoked from C#
// Invoked from C# whenever a new game state version is available
// noinspection JSUnusedGlobalSymbols
async function stateUpdated() {
const blockViews = $('#blocks');
blockViews.empty();

blockStates = await state.GetBlockStates();
for (const entityId in blockStates) {
renderBlock(blockViews, blockStates[entityId]);
async function OnGameStateChange(version) {
// Is the model accessible?
if (TerminalViewModel === undefined)
return;

// Eliminate any any duplicate or redundant calls
if (rendering || version <= displayedVersion)
return;

rendering = true;
try {
let blockIds = await TerminalViewModel.GetModifiedBlockIds(displayedVersion);
let blocks = $('#blocks');
for (const i in blockIds) {
let blockId = blockIds[i];
let blockState = await TerminalViewModel.GetBlockState(blockId);
renderBlock(blocks, blockState);
}
} finally {
displayedVersion = version;
rendering = false;
}
}

function renderBlock(parent, blockState) {
let blockView = $('<div />');
blockView.addClass('block');
blockView.attr('id', 'block-' + blockState.EntityId);
renderBlockInner(blockView, blockState);
parent.append(blockView);
if (blockState == null)
return;

let blockViewId = 'block-' + blockState.Id;
let oldBlockDiv = $('#' + blockViewId);

let blockDiv = $('<div />');
blockDiv.addClass('block');
blockDiv.attr('id', blockViewId);
renderBlockInner(blockDiv, blockState);

if (oldBlockDiv.length === 0)
parent.append(blockDiv);
else
oldBlockDiv.replaceWith(blockDiv);
}

function renderBlockInner(blockView, blockState) {
let id = $('<div />');
id.addClass('entityId');
id.text(blockState.EntityId);
blockView.append(id);

let type = $('<div />');
type.addClass('type');
type.text(blockState.ClassName + ' | ' + blockState.TypeId + ' | ' + blockState.SubtypeName);
blockView.append(type);

let name = $('<div />');
name.addClass('name');
name.text(blockState.Name);
blockView.append(name);

let properties = $('<div />')
properties.addClass('properties');
for (const propertyId in blockState.PropertyStates) {
renderBlockProperty(properties, blockState.PropertyStates[propertyId])
let blockId = blockState.Id;

let idDiv = $('<div />');
idDiv.addClass('id');
idDiv.text('Block #' + blockId);
blockView.append(idDiv);

let typeDiv = $('<div />');
typeDiv.addClass('type');
typeDiv.text(blockState.ClassName + ' | ' + blockState.TypeId + ' | ' + blockState.SubtypeName);
blockView.append(typeDiv);

let nameDiv = $('<div />');
nameDiv.addClass('name');
nameDiv.text(blockState.Name);
blockView.append(nameDiv);

let propertiesDiv = $('<div />')
propertiesDiv.addClass('properties');
for (const propertyId in blockState.Properties) {
renderBlockProperty(propertiesDiv, blockState.Id, blockState.Properties[propertyId])
}
blockView.append(properties)
blockView.append(propertiesDiv)

blockView.append($('<hr />'))
}

function renderBlockProperty(parent, propertyState) {
let propertyView= $('<div />');
propertyView.addClass('property');
function renderBlockProperty(parent, blockId, propertyState) {
let propertyId = propertyState.Id;
let propertyValue = propertyState.Value;

let cb, label;
let propertyDiv= $('<div />');
let propertyDivId = 'block-' + blockId + '-property-' + propertyId;
propertyDiv.attr('id', propertyDivId)
propertyDiv.addClass('property');

let propertyInputId = 'block-' + blockId + '-property-' + propertyId + '-input'

let input, label;
let value = $('<div />');
value.addClass('value');
switch(propertyState.TypeName) {
case "Boolean":
cb = $('<input />')
cb.attr('id', propertyState.Id);
cb.attr('type', 'checkbox');
if (propertyState.Value) {
cb.attr('checked', 'checked');
input = $('<input />')
input.attr('id', propertyInputId);
input.attr('type', 'checkbox');
if (propertyValue) {
input.attr('checked', 'checked');
}
value.append(cb);
value.append(input);

label = $('<label />');
label.attr('for', propertyState.Id);
label.text(propertyState.Id);
label.attr('for', propertyInputId);
label.text(propertyId);
value.append(label);

value.bind('change', async function (e) {
await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.is(':checked'));
});

break;

case "Int64":
label = $('<label />');
label.attr('for', propertyState.Id);
label.text(propertyState.Id);
label.attr('for', propertyInputId);
label.text(propertyId);
value.append(label);

cb = $('<input />')
cb.attr('id', propertyState.Id);
cb.attr('type', 'number');
cb.attr('value', propertyState.Value == null ? '0' : propertyState.Value.toString());
cb.attr('maxlength', '20');
cb.attr('size', '20');
value.append(cb);
input = $('<input />')
input.attr('id', propertyInputId);
input.attr('type', 'number');
input.attr('value', propertyValue == null ? '0' : propertyValue.toString());
input.attr('maxlength', '20');
input.attr('size', '20');
value.append(input);

value.bind('change', async function (e) {
await TerminalViewModel.SetBlockProperty(blockId, propertyId, parseInt(input.value()));
});

break;

case "Single":
label = $('<label />');
label.attr('for', propertyState.Id);
label.text(propertyState.Id);
label.attr('for', propertyInputId);
label.text(propertyId);
value.append(label);

cb = $('<input />')
cb.attr('id', propertyState.Id);
cb.attr('type', 'number');
cb.attr('value', propertyState.Value == null ? '0.0' : propertyState.Value.toString());
cb.attr('maxlength', '20');
cb.attr('size', '20');
value.append(cb);
input = $('<input />')
input.attr('id', propertyInputId);
input.attr('type', 'number');
input.attr('value', propertyValue == null ? '0.0' : propertyValue.toString());
input.attr('maxlength', '20');
input.attr('size', '20');
value.append(input);

value.bind('change', async function (e) {
await TerminalViewModel.SetBlockProperty(blockId, propertyId, parseFloat(input.value()));
});

break;

case "StringBuilder":
label = $('<label />');
label.attr('for', propertyState.Id);
label.text(propertyState.Id);
label.attr('for', propertyInputId);
label.text(propertyId);
value.append(label);

cb = $('<input />')
cb.attr('id', propertyState.Id);
cb.attr('value', propertyState.Value == null ? '' : propertyState.Value.toString());
cb.attr('maxlength', '65535');
cb.attr('size', '100');
value.append(cb);
input = $('<input />')
input.attr('id', propertyInputId);
input.attr('value', propertyValue == null ? '' : propertyValue.toString());
input.attr('maxlength', '65535');
input.attr('size', '100');
value.append(input);

value.bind('change', async function (e) {
await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.value());
});

break;

case "Color":
// FIXME: Add a color picker on click event!
// See https://bitbucket.org/chromiumembedded/cef/issues/899

label = $('<label />');
label.attr('for', propertyState.Id);
label.text(propertyState.Id);
label.attr('for', propertyInputId);
label.text(propertyId);
value.append(label);

cb = $('<input />')
cb.attr('id', propertyState.Id);
cb.attr('type', 'color');
cb.attr('value', propertyState.Value == null ? '#ffffff' : propertyState.Value.toString());
value.append(cb);
input = $('<input />')
input.attr('id', propertyInputId);
input.attr('type', 'color');
input.attr('value', propertyValue == null ? '#ffffff' : propertyValue.toString());
value.append(input);

value.bind('change', async function (e) {
await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.value());
});

break;

default:
label = $('<label />');
label.attr('for', propertyState.Id);
label.text(propertyState.Id + '[' + propertyState.TypeName + '] ');
label.attr('for', propertyInputId);
label.text(propertyId + '[' + propertyState.TypeName + '] ');
value.append(label);

cb = $('<input />')
cb.attr('readonly', 'readonly');
cb.attr('id', propertyState.Id);
cb.attr('value', propertyState.Value == null ? '' : propertyState.Value.toString());
value.append(cb);
input = $('<input />')
input.attr('readonly', 'readonly');
input.attr('id', propertyInputId);
input.attr('value', propertyValue == null ? '' : propertyValue.toString());
value.append(input);

break;
}

propertyView.append(value);
propertyDiv.append(value);

parent.append(propertyView);
}

// Invoked from C#
// noinspection JSUnusedGlobalSymbols
async function blockStateUpdated(entityId) {
// let blockState = await state.GetBlockState(entityId);
// let blockView = $('#block-' + entityId);
// if (blockView.length > 0) {
// renderBlockInner(blockView, blockState);
// }
parent.append(propertyDiv);
}
5 changes: 3 additions & 2 deletions EnhancedUI/Content/Inventory.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<link rel="stylesheet" href="common.css"/>
<link rel="stylesheet" href="Inventory.css"/>
<script src="jquery-3.6.0.min.js"></script>
<script src="common.js"></script>
<script src="Inventory.js"></script>
</head>
<body>

Expand All @@ -20,5 +18,8 @@ <h1 class="show">INVENTORY</h1>

</div>

<script src="common.js"></script>
<script src="Inventory.js"></script>

</body>
</html>
6 changes: 1 addition & 5 deletions EnhancedUI/Content/common.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
$(document).ready(async function () {
// For debugging only
// $("#window-size").text(`${window.innerWidth}x${window.innerHeight}`);

await CefSharp.BindObjectAsync("state");
state.NotifyBound();
await CefSharp.BindObjectAsync("TerminalViewModel");
});
14 changes: 14 additions & 0 deletions EnhancedUI/EnhancedUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@
<PackageReference Include="Winook" Version="1.3.2" />
</ItemGroup>

<ItemGroup>
<Compile Remove="Gui\Terminal\Inventory\InventoryState.cs" />
<Compile Remove="Gui\Terminal\Inventory\MyGuiScreenTerminal_Patch.cs" />
<None Include="Gui\Terminal\Inventory\MyGuiScreenTerminal_Patch.cs" />
<Compile Remove="Gui\Terminal\Inventory\MyTerminalInventoryController_Close_Patch.cs" />
<None Include="Gui\Terminal\Inventory\MyTerminalInventoryController_Close_Patch.cs" />
<Compile Remove="Gui\Terminal\Inventory\MyTerminalInventoryController_HandleInput_Patch.cs" />
<None Include="Gui\Terminal\Inventory\MyTerminalInventoryController_HandleInput_Patch.cs" />
<Compile Remove="Gui\Terminal\Inventory\MyTerminalInventoryController_Init_Patch.cs" />
<None Include="Gui\Terminal\Inventory\MyTerminalInventoryController_Init_Patch.cs" />
<Compile Remove="Gui\Terminal\Inventory\MyTerminalInventoryController_Refresh_Patch.cs" />
<None Include="Gui\Terminal\Inventory\MyTerminalInventoryController_Refresh_Patch.cs" />
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="xcopy /s /e /r /y $(TargetDir)* $(SolutionDir)GameBinaries\Plugins\Local\EnhancedUI" />
<Exec Command="start steam://rungameid/244850" />
Expand Down
Loading

0 comments on commit ed72884

Please sign in to comment.