Update a live OneRender page from C#: set the text of a score readout, toggle a class and disable a button, without building the page again and without losing focus, hover or form state.
using OneRender; using OneRender.Image; using OneRender.Interaction; using OneRender.Painting; using RecursiveAI.OneHtml; string html = """ <!doctype html> <html lang="en"> <style> body { margin: 0; font: 16px sans-serif; background: #16202c; color: #e8edf3 } .hud { display: flex; align-items: center; gap: 12px; padding: 12px 16px } #score { font-weight: bold; min-width: 4em } button { font: inherit; color: inherit; padding: 6px 12px; border: 0; border-radius: 6px; background: #2a3644 } button.on { background: #3b82f6 } button:disabled { opacity: .4 } </style> <div class="hud"> <span>Score</span><span id="score">0</span> <button id="sound">Sound</button> <button id="save">Save</button> </div> </html> """; InteractivePage ui = InteractivePage.Create(html, new HtmlPageOptions(FontProviders.CreateDefault()) { ViewportWidth = 360, ViewportHeight = 60 }); Node score = ui.FindElement("score"); Node sound = ui.FindElement("sound"); Node save = ui.FindElement("save"); ui.Focus(save); // Text: cheap enough to change every frame. InputOutcome o = ui.SetTextContent(score, "1250"); Console.WriteLine($"score: {o}, \"{ui.GetTextContent(score)}\""); // Classes and attributes: for state changes, not every frame. o = ui.ToggleClass(sound, "on", force: true); Console.WriteLine($"sound: {o}, class \"{ui.GetClassName(sound)}\""); o = ui.SetAttribute(save, "disabled", ""); Console.WriteLine($"save: {o}, disabled \"{ui.GetAttribute(save, "disabled")}\", focused: {(ui.FocusedElement.IsNull ? "none" : ui.FocusedElement.Id)}"); Console.WriteLine($"the Node handle still reads the original: {save.GetAttribute("disabled") ?? "null"}"); var list = new DisplayList(); ui.Page.Paint(list); File.WriteAllBytes("hud.png", DisplayListRasterizer.Render(list, 360, 60).ToPng());
score: Relaid, "1250" sound: Relaid, class "on" save: StatesChanged, Relaid, FocusChanged, disabled "", focused: none the Node handle still reads the original: null
In hud.png the score reads 1250, Sound is blue and Save is dimmed by its :disabled rule.
How it works
- Ten calls on
InteractivePage.SetTextContent,SetClassName,AddClass,RemoveClass,ToggleClass,SetAttributeandRemoveAttributechange the page;GetTextContent,GetClassNameandGetAttributeread it back. Each change returns anInputOutcome:Relaidwhen something changed (callRepaintas in Hit testing and events),Nonefor a no-op. - The result equals a fresh page. A page created from the edited HTML lays out and paints the same. Focus, hover, scroll positions, form state and listeners stay as they were, and a focused element that stops being focusable loses focus, with the usual
blurevents. - Your
Nodehandles stay valid, but they point into the document the page was created from, which never changes. Read current values through the page. SetTextContenttakes an element that holds exactly one text node, like a label or a readout. It throws onstyle,script,textarea,title,optionand an element with more than one child.
What each change costs
- Text doesn’t touch the cascade: only the changed text is laid out and painted again. Once warm, a change that fits the room the old text had allocates nothing, so it’s safe to do every frame.
- Classes and attributes restyle from a new snapshot of the document: about 0.8 MB per change on a 1,800-node page. Attributes a control reads when its boxes are made (
value,checked,type,src) rebuild the page, about 7 MB there. - So change text,
transformandopacityper frame, and classes and attributes when something happens, like a click or a state change.
The full rules, including what checked and value do after the user has changed a control, are in Game hosts.