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 render(self) -> None: self.render_headers() # TODO: Make the vertical scrollbar always visible? ig.begin_child('Frame Sheet Rows', flags=ig.WINDOW_ALWAYS_VERTICAL_SCROLLBAR) self.update_scolling() min_frame = int(ig.get_scroll_y()) // self.row_height - 1 self.sequence.set_hotspot('frame-sheet-min', max(min_frame, 0)) if self.dragging and not ig.is_mouse_down(): self.drag_handler.release_drag() self.dragging = False self.render_rows() ig.end_child() self.columns = list(self.next_columns)
def render_frame_slider( id: str, current_frame: int, num_frames: int, loaded_frames: List[int] = [], ) -> Maybe[int]: ig.push_id(id) pos = ig.get_cursor_pos() pos = ( pos[0] + ig.get_window_position()[0], pos[1] + ig.get_window_position()[1] - ig.get_scroll_y(), ) width = ig.get_content_region_available_width() ig.push_item_width(width) changed, new_frame = ig.slider_int( '##slider', current_frame, 0, num_frames - 1, ) ig.pop_item_width() dl = ig.get_window_draw_list() for frame in loaded_frames: line_pos = pos[0] + frame / num_frames * width dl.add_line( line_pos, pos[1] + 13, line_pos, pos[1] + 18, ig.get_color_u32_rgba(1, 0, 0, 1), ) ig.pop_id() if changed: return Just(new_frame) else: return None
def render_input_text_with_error( id: str, value: str, buffer_size: int, width: int, validate: Callable[[str], T], ) -> Maybe[T]: ig.push_id(id) 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(width) changed, new_value = ig.input_text('##input', value, buffer_size) ig.pop_item_width() result_value = None if changed: try: result_value = Just(validate(new_value)) except: # TODO: Show error message dl = ig.get_window_draw_list() dl.add_rect( cursor_pos[0], cursor_pos[1], cursor_pos[0] + width, cursor_pos[1] + ig.get_text_line_height() + 2 * ig.get_style().frame_padding[1], ig.get_color_u32_rgba(1, 0, 0, 1), ) new_value = value ig.pop_id() return result_value
def update_scolling(self) -> None: self.scroll_delta = 0.0 if self.sequence.selected_frame == self.prev_selected_frame: return self.prev_selected_frame = self.sequence.selected_frame target_y = self.sequence.selected_frame * self.row_height curr_scroll_y = ig.get_scroll_y() current_min_y = curr_scroll_y current_max_y = curr_scroll_y + ig.get_window_height( ) - self.row_height if target_y > current_max_y: new_scroll_y = target_y - ig.get_window_height() + self.row_height elif target_y < current_min_y: new_scroll_y = target_y else: return ig.set_scroll_y(new_scroll_y) # Account for one frame set_scroll_y delay to prevent flickering self.scroll_delta = new_scroll_y - curr_scroll_y
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_joystick_control( id: str, stick_x: float, stick_y: float, shape='square', ) -> Optional[Tuple[float, float]]: ig.push_id(id) state = use_state('', JoystickControlState()).value dl = ig.get_window_draw_list() padding = 10 content_region = ig.get_content_region_available() size = min( content_region.x - ig.get_style().scrollbar_size - 2 * padding, content_region.y - 2 * padding, 200, ) size = max(size, 100) initial_cursor_pos = ig.get_cursor_pos() top_left = ( initial_cursor_pos[0] + ig.get_window_position()[0] - ig.get_scroll_x() + padding, initial_cursor_pos[1] + ig.get_window_position()[1] - ig.get_scroll_y() + padding, ) background_color = ig.get_color_u32_rgba(0, 0, 0, 0.3) if shape == 'square': dl.add_rect_filled( top_left[0], top_left[1], top_left[0] + size, top_left[1] + size, background_color, ) elif shape == 'circle': dl.add_circle_filled( top_left[0] + size / 2, top_left[1] + size / 2, size / 2, background_color, num_segments=32, ) result = None if state.active and ig.is_mouse_down(): new_offset = state.get_value(ig.get_mouse_drag_delta(lock_threshold=0)) new_stick_x = new_offset[0] / size * 2 - 1 new_stick_y = (1 - new_offset[1] / size) * 2 - 1 if shape == 'square': new_stick_x = min(max(new_stick_x, -1), 1) new_stick_y = min(max(new_stick_y, -1), 1) elif shape == 'circle': mag = math.sqrt(new_stick_x**2 + new_stick_y**2) if mag > 1: new_stick_x /= mag new_stick_y /= mag if (new_stick_x, new_stick_y) != (stick_x, stick_y): stick_x, stick_y = new_stick_x, new_stick_y result = (stick_x, stick_y) offset = ( (stick_x + 1) / 2 * size, (1 - (stick_y + 1) / 2) * size, ) dl.add_line( top_left[0] + size / 2, top_left[1] + size / 2, top_left[0] + offset[0], top_left[1] + offset[1], ig.get_color_u32_rgba(1, 1, 1, 0.5), ) button_size = 20 button_pos = ( padding + initial_cursor_pos[0] + offset[0] - button_size / 2, padding + initial_cursor_pos[1] + offset[1] - button_size / 2, ) ig.set_cursor_pos(button_pos) ig.button('##joystick-button', button_size, button_size) ig.set_cursor_pos(( initial_cursor_pos[0], initial_cursor_pos[1] + size + 2 * padding, )) if ig.is_item_active(): state.set_active(offset) else: state.reset() ig.pop_id() return result
def render_rows(self) -> None: ig.columns(len(self.columns) + 1) min_row = int(ig.get_scroll_y() + self.scroll_delta) // self.row_height - 1 min_row = max(min_row, 0) max_row = int(ig.get_scroll_y() + self.scroll_delta + ig.get_window_height()) // self.row_height # max_row = min(max_row, self.get_row_count() - 1) self.sequence.extend_to_frame(max_row + 100) timeline_operations: List[Callable[[], None]] = [] mouse_pos = ( ig.get_mouse_pos().x - ig.get_window_position().x, ig.get_mouse_pos().y - ig.get_window_position().y + ig.get_scroll_y() + self.scroll_delta, ) for row in range(min_row, max_row + 1): row_pos = (0.0, row * self.row_height - self.scroll_delta) ig.set_cursor_pos(row_pos) mouse_in_row = mouse_pos[1] > row_pos[1] and mouse_pos[ 1] <= row_pos[1] + self.row_height if mouse_in_row and self.dragging and time.time( ) - self.time_started_dragging > 0.1: self.drag_handler.update_drag(row) if len(self.columns) > 0: ig.set_column_width(-1, self.frame_column_width) clicked, _ = ig.selectable( str(row) + '##fs-framenum-' + str(id(self)) + '-' + str(row), row == self.sequence.selected_frame, height=self.row_height - 8, # TODO: Compute padding ) if clicked: self.sequence.set_selected_frame(row) if ig.begin_popup_context_item('##fs-framenumctx-' + str(id(self)) + '-' + str(row)): if ig.selectable('Insert above')[0]: def op(row): return lambda: self.sequence.insert_frame(row) timeline_operations.append(op(row)) if ig.selectable('Insert below')[0]: def op(row): return lambda: self.sequence.insert_frame(row + 1) timeline_operations.append(op(row)) if ig.selectable('Delete')[0]: def op(row): return lambda: self.sequence.delete_frame(row) timeline_operations.append(op(row)) ig.end_popup_context_item() ig.next_column() for column in self.columns: self.render_cell(row, column) ig.set_column_width(-1, column.width) ig.next_column() ig.separator() ig.set_cursor_pos((0, (self.sequence.max_frame + 1) * self.row_height)) ig.columns(1) for operation in timeline_operations: operation()