def _render_checkbox( value: T, formatter: VariableFormatter, size: Tuple[int, int], highlight: bool, ) -> Tuple[Maybe[T], bool, bool]: cursor_pos = ig.get_cursor_pos() _, input = ig.checkbox('##checkbox', dcast(bool, formatter.output(value))) ig.set_cursor_pos(cursor_pos) clicked, _ = ig.selectable( '##checkbox-background', highlight, width=size[0], height=size[1], ) pressed = ig.is_item_hovered() and ig.is_mouse_clicked() input_value = formatter.input(input) assert type(input_value) == type(value) if input_value != value: return Just(cast(T, input_value)), clicked, pressed else: return None, clicked, pressed
def render_headers(self) -> None: header_labels = [ self.displayer.column_header(column.variable) for column in self.columns ] header_lines = max((len(label.split('\n')) for label in header_labels), default=1) ig.columns(len(self.columns) + 1) if len(self.columns) > 0: ig.set_column_width(-1, self.frame_column_width) ig.text('') ig.next_column() for index, column in enumerate(self.columns): initial_cursor_pos = ig.get_cursor_pos() ig.selectable( '##fs-col-' + str(id(self)) + '-' + str(id(column)), height=header_lines * ig.get_text_line_height(), ) # TODO: Width adjusting ig.set_column_width(-1, column.width) if ig.begin_drag_drop_source(): ig.text(header_labels[index]) ig.set_drag_drop_payload('fs-col', str(index).encode('utf-8')) ig.end_drag_drop_source() if ig.begin_drag_drop_target(): payload = ig.accept_drag_drop_payload('fs-col') if payload is not None: source = int(payload.decode('utf-8')) self._move_column(source, index) payload = ig.accept_drag_drop_payload('ve-var') if payload is not None: self._insert_variable(index, Variable.from_bytes(payload)) ig.end_drag_drop_target() if ig.is_item_hovered() and ig.is_mouse_clicked(2): self._remove_column(index) if ig.begin_popup_context_item('##fs-colctx-' + str(id(self)) + '-' + str(id(column))): if ig.selectable('Close')[0]: self._remove_column(index) ig.end_popup_context_item() ig.set_cursor_pos(initial_cursor_pos) ig.text(header_labels[index]) ig.next_column() ig.separator() ig.columns(1)
def render_labeled_variable( id: str, label: str, variable: Variable, value: T, formatter: VariableFormatter, is_edited: bool, label_width=80, value_width=80, ) -> Tuple[Maybe[T], bool]: ig.push_id(id) ig.selectable(label + '##label', width=label_width) if ig.begin_drag_drop_source(): ig.text(label) ig.set_drag_drop_payload('ve-var', variable.to_bytes()) ig.end_drag_drop_source() ig.same_line() cell_width = value_width cell_height = ig.get_text_line_height( ) + 2 * ig.get_style().frame_padding[1] cell_cursor_pos = ig.get_cursor_pos() cell_cursor_pos = ( cell_cursor_pos[0] + ig.get_window_position()[0] - ig.get_scroll_x(), cell_cursor_pos[1] + ig.get_window_position()[1] - ig.get_scroll_y(), ) changed_data, _, _ = render_variable_value( 'value', value, formatter, (cell_width, cell_height), ) clear_edit = is_edited and ig.is_item_hovered() and ig.is_mouse_down(2) if is_edited: dl = ig.get_window_draw_list() spacing = ig.get_style().item_spacing spacing = (spacing[0] / 2, spacing[1] / 2) dl.add_rect( cell_cursor_pos[0] - spacing[0], cell_cursor_pos[1] - spacing[1], cell_cursor_pos[0] + cell_width + spacing[0] - 1, cell_cursor_pos[1] + cell_height + spacing[1] - 1, ig.get_color_u32_rgba(0.8, 0.6, 0, 1), ) ig.pop_id() return changed_data, clear_edit
def render_variable_cell( id: str, value: T, formatter: VariableFormatter, cell_size: Tuple[int, int], is_selected: bool, frame: Optional[int] = None, highlight_range: Optional[Tuple[range, ig.Color4f]] = None, ) -> Tuple[Maybe[T], bool, bool, bool]: ig.push_id(id) window_pos = ig.get_window_position() item_spacing = ig.get_style().item_spacing cell_cursor_pos = ig.get_cursor_pos() cell_cursor_pos = ( cell_cursor_pos.x + window_pos.x - item_spacing.x, cell_cursor_pos.y + window_pos.y - ig.get_scroll_y() - item_spacing.y, ) if highlight_range is not None: assert frame is not None frames, color = highlight_range margin = 5 offset_top = margin if frame == frames.start else 0 offset_bottom = margin if frame == frames.stop - 1 else 0 dl = ig.get_window_draw_list() dl.add_rect_filled( cell_cursor_pos[0] + margin, cell_cursor_pos[1] + offset_top, cell_cursor_pos[0] + cell_size[0] - margin, cell_cursor_pos[1] + cell_size[1] - offset_bottom, ig.get_color_u32_rgba(*color), ) changed_data, selected, pressed = render_variable_value( 'value', value, formatter, ( cell_size[0] - 2 * item_spacing.x, cell_size[1] - 2 * item_spacing.y, ), highlight=is_selected, ) clear_edit = ig.is_item_hovered() and ig.is_mouse_down(2) ig.pop_id() return changed_data, clear_edit, selected, pressed
def binding_button(name: str, label: str, width=0) -> None: listening_for: Ref[Optional[str]] = use_state('listening-for', None) controls: Ref[List[str]] = use_state('controls', []) controls.value.append(name) value = check_input(bindings.get(name)) color = (0.26 + value * 0.7, 0.59 + value * 0.41, 0.98, 0.4) if listening_for.value == name: color = (0.86, 0.59, 0.98, 0.4) ig.push_style_color(ig.COLOR_BUTTON, *color) if ig.button(label, width=width): listening_for.value = name ig.pop_style_color() if ig.begin_popup_context_item('##btn-ctx-' + name): if ig.menu_item('Clear')[0] and name in bindings: del bindings[name] if ig.menu_item('Default')[0] and name in DEFAULT_BINDINGS: bindings[name] = DEFAULT_BINDINGS[name] ig.end_popup_context_item() ig.same_line() if name in bindings: text = str(bindings[name]) else: text = 'Unbound' if listening_for.value == name: text = '(' + text + ')' if name in bindings: overlapping = find_overlapping_bindings().get(bindings[name]) else: overlapping = None if overlapping is None: ig.text(text) else: ig.text_colored(text, 1.0, 0.8, 0.0, 1.0) if ig.is_item_hovered(): def control_name(name: str) -> str: return name.replace('-', ' ').replace('n64', 'N64') overlapping = [c for c in overlapping if c != name] ig.set_tooltip('Also bound to ' + ', '.join(map(control_name, overlapping)))
def _render_text( value: T, formatter: VariableFormatter, size: Tuple[int, int], highlight: bool, ) -> Tuple[Maybe[T], bool, bool]: editing = use_state('editing', False) initial_focus = use_state('initial-focus', False) if not editing.value: clicked, _ = ig.selectable( dcast(str, formatter.output(value)) + '##text', highlight, width=size[0], height=size[1], flags=ig.SELECTABLE_ALLOW_DOUBLE_CLICK, ) if clicked: if ig.is_mouse_double_clicked(): editing.value = True initial_focus.value = False pressed = ig.is_item_hovered() and ig.is_mouse_clicked() return None, clicked, pressed cursor_pos = ig.get_cursor_pos() cursor_pos = ( ig.get_window_position()[0] + cursor_pos[0], ig.get_window_position()[1] + cursor_pos[1] - ig.get_scroll_y(), ) ig.push_item_width(size[0]) value_text = dcast(str, formatter.output(value)) buffer_size = len(value_text) + ig.get_clipboard_length() + 1000 _, input = ig.input_text('##text-edit', value_text, buffer_size) ig.pop_item_width() if not initial_focus.value: ig.set_keyboard_focus_here(-1) initial_focus.value = True elif not ig.is_item_active(): editing.value = False try: input_value = formatter.input(input) assert type(input_value) is type(value) if input_value != value: return Just(cast(T, input_value)), False, False except: # TODO: Show error message dl = ig.get_window_draw_list() dl.add_rect( cursor_pos[0], cursor_pos[1], cursor_pos[0] + size[0], cursor_pos[1] + ig.get_text_line_height() + 2 * ig.get_style().frame_padding[1], ig.get_color_u32_rgba(1, 0, 0, 1), ) return None, False, False
def render_tabs( id: str, tabs: List[TabInfo], open_tab_index: Optional[int] = None, allow_windowing=False, ) -> Tuple[Optional[int], Optional[int]]: ig.push_id(id) root_id = get_local_state_id_stack() ig.columns(2) closed_tab = None rendered = use_state('rendered', False) if not rendered.value: rendered.value = True ig.set_column_width(-1, 120) if len(tabs) == 0: ig.pop_id() return None, closed_tab selected_tab_index = use_state_with('selected-tab-index', lambda: open_tab_index or 0) selected_tab_id = use_state_with('selected-tab', lambda: tabs[selected_tab_index.value].id) if open_tab_index is not None: selected_tab_index.value = open_tab_index selected_tab_id.value = tabs[open_tab_index].id windowed_tabs = use_state('windowed-tabs', cast(Set[str], set())).value # TODO: Change selected tab if windowed # Handle deletion/insertion if selected_tab_index.value >= len(tabs): selected_tab_index.value = len(tabs) - 1 if tabs[selected_tab_index.value].id != selected_tab_id.value: matching_indices = [ i for i in range(len(tabs)) if tabs[i].id == selected_tab_id.value ] if len(matching_indices) > 0: selected_tab_index.value = matching_indices[0] else: selected_tab_id.value = tabs[selected_tab_index.value].id ig.begin_child('tabs') for i, tab in enumerate(tabs): if tab.id in windowed_tabs: continue _, selected = ig.selectable( tab.label + '##tab-' + tab.id, selected_tab_id.value == tab.id, ) if selected: selected_tab_index.value = i selected_tab_id.value = tab.id if tab.closable and ig.is_item_hovered() and ig.is_mouse_clicked(2): closed_tab = i if allow_windowing or tab.closable: if ig.begin_popup_context_item(f'##ctx-{tab.id}'): if allow_windowing and ig.selectable('Pop out')[0]: windowed_tabs.add(tab.id) if tab.closable and ig.selectable('Close')[0]: closed_tab = i ig.end_popup_context_item() ig.end_child() ig.next_column() ig.begin_child('content', flags=ig.WINDOW_HORIZONTAL_SCROLLING_BAR) tab = tabs[selected_tab_index.value] if tab.id not in windowed_tabs: push_local_state_rebase(('rebase-tabs', ) + root_id) tab.render(tab.id) # type: ignore pop_local_state_rebase() ig.end_child() ig.columns(1) for tab_id in set(windowed_tabs): matching = [tab for tab in tabs if tab.id == tab_id] if len(matching) == 0: windowed_tabs.remove(tab.id) continue tab = matching[0] ig.set_next_window_size(*ig.get_window_size(), ig.ONCE) ig.set_next_window_position(*ig.get_window_position(), ig.ONCE) ig.push_style_color(ig.COLOR_WINDOW_BACKGROUND, 0.06, 0.06, 0.06, 0.94) _, opened = ig.begin( tab.label + '##window-' + tab.id, closable=True, flags=ig.WINDOW_HORIZONTAL_SCROLLING_BAR, ) push_local_state_rebase(('rebase-tabs', ) + root_id) tab.render(tab.id) # type: ignore pop_local_state_rebase() ig.end() ig.pop_style_color() if not opened: windowed_tabs.remove(tab.id) ig.pop_id() return ( None if open_tab_index == selected_tab_index.value else selected_tab_index.value, closed_tab, )