I'm encountering a problem with the UI design

Isaac Sim Version

√5.1.0
√ 4.5.0

Operating System

√Windows 11

GPU Information

  • ---------------------------------------------------------------------------------------------|
    | Driver Version: 581.57 | Graphics API: Vulkan
    | NVIDIA GeForce RTX 3070 Ti Lap..

Detailed Description

I am encountering an issue with the UI design while attempting to extend a TreeView structure to create an interface similar to an Excel spreadsheet. The same code works correctly in Isaac Sim 4.5, both in the Script Editor and in custom extensions. However, in Isaac Sim 5.1, double-clicking often fails to activate table editing, and after some time, the double-click interaction stops working entirely. The code is provided below and can be run directly in the Script Editor.

__all__ = ["ReflectanceDatabaseWindow"]

import omni.ui as ui
import asyncio
import omni.kit.app

LABEL_HEIGHT = 34 
SPACING = 8

# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
global my_db_window
try:
    if my_db_window:
        my_db_window.destroy()
except NameError:
    my_db_window = None

# ---------------------------------------------------------------------------
# Model
# ---------------------------------------------------------------------------
class FloatModel(ui.AbstractValueModel):
    def __init__(self, value: float):
        super().__init__()
        self._value = value
    def get_value_as_float(self): return self._value or 0.0
    def get_value_as_string(self): return "{0:g}".format(self._value) if self._value is not None else ""
    def set_value(self, value):
        try:
            val = float(value)
            if val != self._value:
                self._value = val
                self._value_changed()
        except ValueError: pass

class TableItem(ui.AbstractItem):
    def __init__(self, param_name, value, note):
        super().__init__()
        self.name_model = ui.SimpleStringModel(str(param_name))
        self.value_model = FloatModel(float(value))
        self.note_model = ui.SimpleStringModel(str(note))

class TableModel(ui.AbstractItemModel):
    def __init__(self, headers, data):
        super().__init__()
        self.headers = headers
        self._items = [TableItem(*row) for row in data]
    def get_item_children(self, item): return self._items if item is None else []
    def get_item_value_model_count(self, item): return len(self.headers)
    def get_item_value_model(self, item, column_id):
        if item is None: return None
        return [item.name_model, item.value_model, item.note_model][column_id]

# ---------------------------------------------------------------------------
#  (Delegate)
# ---------------------------------------------------------------------------
class TableDelegate(ui.AbstractItemDelegate):
    def __init__(self, model):
        super().__init__()
        self._model = model
        self._subscriptions = {} 

    def build_branch(self, model, item, column_id, level, expanded):
        pass 

    def build_header(self, column_id):
        title = self._model.headers[column_id] if self._model and column_id < len(self._model.headers) else ""
        with ui.ZStack(height=LABEL_HEIGHT):
            ui.Rectangle(style={"background_color": 0xFF3A3A3A, "border_color": 0xFF555555, "border_width": 1})
            ui.Label(title, alignment=ui.Alignment.CENTER, style={"color": 0xFFAAAAAA, "font_size": 14})

    def build_widget(self, model, item, column_id, level, expanded):
        if item is None: return
        value_model = model.get_item_value_model(item, column_id)
        is_float_col = (column_id == 1)

        stack = ui.ZStack(height=24)
        with stack:

            ui.Rectangle(style={"background_color": 0xFF222222, "border_color": 0xFF444444, "border_width": 1})
            

            with ui.Placer(offset_x=5):
                label = ui.Label(value_model.as_string, alignment=ui.Alignment.LEFT_CENTER)
            

            hit_cover = ui.Rectangle(style={"background_color": 0x01000000}, visible=True)

            edit_style = {"background_color": 0xFF000000}
            if is_float_col:
                field = ui.FloatField(value_model, visible=False, style=edit_style)
            else:
                field = ui.StringField(value_model, visible=False, style=edit_style)

        hit_cover.set_mouse_double_clicked_fn(
            lambda x, y, b, m, f=field, l=label: self._start_edit(b, f, l)
        )

    def _start_edit(self, button, field, label):
        if button != 0: return 
        
        field.visible = True
        

        async def focus_with_delay():
            await omni.kit.app.get_app().next_update_async()
            field.focus_keyboard()
        
        asyncio.ensure_future(focus_with_delay())

        sub_id = id(field)
        self._subscriptions[sub_id] = field.model.subscribe_end_edit_fn(
            lambda m, f=field, l=label, sid=sub_id: self._end_edit(m, f, l, sid)
        )

    def _end_edit(self, model, field, label, sub_id):
        field.visible = False
        label.text = model.as_string
        if sub_id in self._subscriptions:
            del self._subscriptions[sub_id]

class ReflectanceDatabaseWindow(ui.Window):
    def __init__(self, title: str, **kwargs):
        super().__init__(title, **kwargs)
        
        # 测试数据
        headers = ["Name", "Reflectance", "Transmittance"]
        data = [
            ["leaf", "100", "Max velocity"],
            ["branch", "0.5", "Surface friction"],
            ["ground", "0.1", "Restitution"],
            ["water", "-9.8", "Standard Earth"],
        ]

        self._model = TableModel(headers, data)
        self._delegate = TableDelegate(self._model)

        self.frame.set_build_fn(self._build_fn_observation)

    def destroy(self):
        super().destroy()
        self._model = None
        self._delegate = None

    def _build_fn_observation(self):
        with ui.VStack(spacing=5):
            ui.Label("ReflectanceDatabase (Fixed)", height=20, style={"color": 0xFF88FF88})
            
            with ui.ScrollingFrame(
                horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_OFF,
                vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON
            ):
                self._tree_view = ui.TreeView(
                    self._model,
                    delegate=self._delegate,
                    root_visible=False,
                    header_visible=True,
                    columns_resizable=True,
                    column_widths=[ui.Fraction(1), ui.Fraction(1), ui.Fraction(2)]
                )

# ---------------------------------------------------------------------------
# run
# ---------------------------------------------------------------------------
my_db_window = ReflectanceDatabaseWindow("Reflectance DB", width=500, height=400)

Code reference: omni.example.ui.treeview_doc.py

def build_widget(self, model, item, column_id, level, expanded):
    """Create a widget per column per item"""
    stack = ui.ZStack(height=20)
    with stack:
        value_model = model.get_item_value_model(item, column_id)
        label = ui.Label(value_model.as_string)
        if column_id == 1:
            field = ui.FloatField(value_model, visible=False)
        else:
            field = ui.StringField(value_model, visible=False)

    # Start editing when double clicked
    stack.set_mouse_double_clicked_fn(lambda x, y, b, m, f=field, l=label:                    self.on_double_click(b, f, l))

Keeping the build_widget function consistent with the code in treeview_doc.py will not change anything.

Could you please try setting
runLoops.main.manualModeEnabled = false
in your Kit experience file (e.g. apps/isaacsim.exp.full.kit) and see if this works around the issue?

This setting ensures that UI field interactions (like double-click edits) run in the normal update loop rather than manual mode, which can restore expected behavior in some cases. Please let us know if this resolves the issue on your end.

Thank you for your reply. The code runLoops.main.manualModeEnabled = false was very helpful and perfectly solved my problem.

The fix for this double‑click issue is included in Isaac Sim 6.0 OSS. Please try upgrading to 6.0 and let us know if you still see the problem there.