def begin_binding_form() -> None: controls: Ref[List[str]] = use_state('controls', []) listening_for: Ref[Optional[str]] = use_state('listening-for', None) waiting_for_release: Ref[bool] = use_state('waiting-for-release', False) if ig.is_mouse_clicked(): listening_for.value = None if listening_for.value is not None: input = detect_input() if input is not None and not waiting_for_release.value: bindings[listening_for.value] = input config.settings['bindings'] = bindings_to_json(bindings) if listening_for.value in controls.value: index = controls.value.index(listening_for.value) + 1 listening_for.value = controls.value[index] if index < len( controls.value) else None waiting_for_release.value = True else: listening_for.value = None if input is None: waiting_for_release.value = False controls.value = []
def do_render(id: str) -> None: nonlocal view if view is None: view = View(model) ig.push_id(id) log.timer.begin('render') view.render() log.timer.end() ig.pop_id() last_fps_time = use_state_with('last-fps-time', lambda: time.time()) frame_count = use_state('frame-count', 0) fps = use_state('fps', 0.0) if hasattr(model, 'pipeline'): frame_count.value += 1 if time.time() > last_fps_time.value + 5: fps.value = frame_count.value / (time.time() - last_fps_time.value) last_fps_time.value = time.time() frame_count.value = 0 log.info( f'mspf: {int(1000 / fps.value * 10) / 10} ({int(fps.value)} fps)' f' - cache={model.pipeline.data_cache_size() // 1024}KB' ) log.timer.begin('balance') model.pipeline.balance_distribution(1/120) log.timer.end()
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 input_pressed(name: str) -> bool: ig.push_id('ctrl-pressed-' + name) prev_down = use_state('prev-down', False) down = _input_down_uncond(name) pressed = not prev_down.value and down prev_down.value = down ig.pop_id() return pressed if ig.global_keyboard_capture() else False
def render_right_column(self) -> None: total_height = ig.get_window_height() if self.show_debug_pane: ig.push_id('debug-pane') ig.begin_child('##pane', height=int(ig.get_window_height() * 0.15)) ig.columns(2) ig.set_column_width(-1, ig.get_window_width() - 300) ig.begin_child('##log') def init_log() -> List[str]: messages = [] log.subscribe(lambda msg: messages.append(str(msg))) return messages messages = use_state_with('messages', init_log).value prev_length = use_state('prev-length', 0) total_height -= ig.get_window_height() if prev_length.value != len(messages): prev_length.value = len(messages) ig.set_scroll_y(ig.get_scroll_max_y() + ig.get_window_height()) for message in messages: ig.text(message) ig.end_child() ig.next_column() for line in log.timer.format(log.timer.get_summaries()): ig.text(line) ig.columns(1) ig.end_child() ig.pop_id() log.timer.begin('fsheet') frame_sheet = self.frame_sheets[0] ig.set_next_window_content_size(frame_sheet.get_content_width(), 0) ig.begin_child( 'Frame Sheet##' + str(epoch) + '-0', height=int(total_height * 0.7), flags=ig.WINDOW_HORIZONTAL_SCROLLING_BAR, ) frame_sheet.render() ig.end_child() if ig.begin_drag_drop_target(): payload = ig.accept_drag_drop_payload('ve-var') if payload is not None: frame_sheet.append_variable(Variable.from_bytes(payload)) ig.end_drag_drop_target() log.timer.end() log.timer.begin('varexp') ig.begin_child('Variable Explorer', border=True) self.variable_explorer.render('variable-explorer') ig.end_child() log.timer.end()
def input_pressed_repeat(name: str, delay: float, per_second: float) -> int: ig.push_id('ctrl-pressed-repeat-' + name) down_start: Ref[Optional[float]] = use_state('down-start', None) counted = use_state('counted', 0) down = _input_down_uncond(name) if not down: down_start.value = None count = 0 elif down_start.value is None: down_start.value = time.time() counted.value = 0 count = 1 else: held = time.time() - down_start.value total_count = int(per_second * max(held - delay, 0)) count = total_count - counted.value counted.value = total_count ig.pop_id() return count if ig.global_keyboard_capture() else 0
def input_down_gradual(name: str, until_full: float) -> float: ig.push_id('ctrl-down-gradual-' + name) down_start: Ref[Optional[float]] = use_state('down-start', None) down = _input_down_uncond(name) if not down: down_start.value = None result = 0.0 elif down_start.value is None: down_start.value = time.time() result = 0.0 else: result = min((time.time() - down_start.value) / until_full, 1) ig.pop_id() return result if ig.global_keyboard_capture() else 0.0
def render(self) -> None: ig.push_id(str(epoch)) # if ig.is_key_pressed(ord('`')): # self.show_debug_pane = not self.show_debug_pane self.handle_controller() prev_frame_time = use_state_with('prev-frame-time', time.time) accum_time = use_state('accum-time', 0.0) now = time.time() accum_time.value += now - prev_frame_time.value prev_frame_time.value = now play_speed = self.model.play_speed if play_speed == 0.0: accum_time.value = 0 else: target_fps = 30 * abs(play_speed) target_dt = 1 / target_fps updates = 0 while accum_time.value >= target_dt and updates < 20: accum_time.value -= target_dt self.model.selected_frame += 1 if play_speed > 0 else -1 self.handle_controller() updates += 1 ig_window_size = ig.get_window_size() window_size = (int(ig_window_size.x), int(ig_window_size.y)) ig.columns(2) self.render_left_column(window_size) ig.next_column() self.render_right_column() ig.columns(1) ig.pop_id()
def handle_controller(self) -> None: ig.push_id('controller-inputs') buttons_enabled = use_state('buttons-enabled', False) stick_enabled = use_state('stick-enabled', False) def add_callbacks() -> Ref[bool]: input_edit = Ref(False) def disable_controller(*args, **kwargs) -> None: if not input_edit.value: buttons_enabled.value = False stick_enabled.value = False self.model.on_edit(disable_controller) def frame_change(*args, **kwargs) -> None: if self.model.play_speed == 0.0: disable_controller() self.model.on_selected_frame_change(frame_change) return input_edit input_edit = use_state_with('initialize', add_callbacks).value prev_play_speed = use_state('prev-play-speed', 0.0) if self.model.play_speed != prev_play_speed.value: buttons_enabled.value = False stick_enabled.value = False prev_play_speed.value = self.model.play_speed controller_button_values = { 'input-button-a': input_down('n64-A'), 'input-button-b': input_down('n64-B'), 'input-button-z': input_down('n64-Z'), 'input-button-s': input_down('n64-S'), 'input-button-l': input_down('n64-L'), 'input-button-r': input_down('n64-R'), 'input-button-cu': input_down('n64-C^'), 'input-button-cl': input_down('n64-C<'), 'input-button-cr': input_down('n64-C>'), 'input-button-cd': input_down('n64-Cv'), 'input-button-du': input_down('n64-D^'), 'input-button-dl': input_down('n64-D<'), 'input-button-dr': input_down('n64-D>'), 'input-button-dd': input_down('n64-Dv'), } if any(controller_button_values.values()): buttons_enabled.value = True stick_enabled.value = True for variable_name, new_button_value in controller_button_values.items(): variable = Variable(variable_name).with_frame(self.model.selected_frame) button_value = self.model.get(variable) if buttons_enabled.value and button_value != new_button_value: input_edit.value = True self.model.set(variable, new_button_value) input_edit.value = False controller_stick_values = ( input_float('n64->') - input_float('n64-<'), input_float('n64-^') - input_float('n64-v'), ) # Require a larger magnitude for enabling controller since dead zone may be too small if any(abs(v) > 0.1 for v in controller_stick_values): stick_enabled.value = True buttons_enabled.value = True if stick_enabled.value: stick_x_var = Variable('input-stick-x').with_frame(self.model.selected_frame) stick_y_var = Variable('input-stick-y').with_frame(self.model.selected_frame) new_stick = self.compute_stick_from_controller(*controller_stick_values) stick = (self.model.get(stick_x_var), self.model.get(stick_y_var)) if stick != new_stick: input_edit.value = True self.model.set(stick_x_var, new_stick[0]) self.model.set(stick_y_var, new_stick[1]) input_edit.value = False ig.pop_id()
def render_left_column(self, framebuffer_size: Tuple[int, int]) -> None: total_height = ig.get_window_height() - ig.get_frame_height() # subtract menu bar slider_space = 45 wall_hitbox_radius = use_state('wall-hitbox-radius', 50) wall_hitbox_options = [0, 24, 50, 110] hovered_surface: Ref[Optional[int]] = use_state('hovered-surface', None) new_hovered_surface: Optional[int] = None hidden_surfaces_by_area = \ use_state('hidden-surfaces', cast(Dict[Tuple[int, int], Set[int]], {})).value current_area = ( dcast(int, self.model.get(Variable('level-num').with_frame(self.model.selected_frame))), dcast(int, self.model.get(Variable('area-index').with_frame(self.model.selected_frame))), ) hidden_surfaces = hidden_surfaces_by_area.setdefault(current_area, set()) log.timer.begin('gview1') ig.begin_child( 'Game View 1', height=int(total_height // 2) - slider_space // 2, border=True, ) hovered_surface_1 = ui.render_game_view_rotate( 'game-view-1', framebuffer_size, self.model, wall_hitbox_radius.value, hovered_surface.value, hidden_surfaces, ) ig.set_cursor_pos((10.0, ig.get_window_height() - 30)) ig.text('wall radius') ig.same_line() ig.push_item_width(50) _, index = ig.combo( '##wall-hitbox-radius', wall_hitbox_options.index(wall_hitbox_radius.value), list(map(str, wall_hitbox_options)), ) wall_hitbox_radius.value = wall_hitbox_options[index] ig.pop_item_width() ig.end_child() log.timer.end() log.timer.begin('gview2') ig.begin_child( 'Game View 2', height=int(total_height // 2) - slider_space // 2, border=True, ) hovered_surface_2 = ui.render_game_view_birds_eye( 'game-view-2', framebuffer_size, self.model, wall_hitbox_radius.value, hovered_surface.value, hidden_surfaces, ) ig.end_child() log.timer.end() new_hovered_surface = hovered_surface_1 or hovered_surface_2 if new_hovered_surface is not None and ig.is_mouse_clicked(1): ig.open_popup('surface-ctx') hovered_surface.value = new_hovered_surface if ig.begin_popup('surface-ctx'): if hovered_surface.value is not None: if hovered_surface.value in hidden_surfaces: if ig.menu_item('Show')[0]: hidden_surfaces.remove(hovered_surface.value) else: if ig.menu_item('Hide')[0]: hidden_surfaces.add(hovered_surface.value) if ig.menu_item('Properties')[0]: self.variable_explorer.open_surface_tab(hovered_surface.value) ig.end_popup() else: hovered_surface.value = new_hovered_surface if hovered_surface.value is not None and ig.is_mouse_clicked(2): if hovered_surface.value in hidden_surfaces: hidden_surfaces.remove(hovered_surface.value) else: hidden_surfaces.add(hovered_surface.value) speed_options = [0.05, 0.25, 0.5, 1, 2, 4] saved_play_direction = use_state('saved-play-direction', 0) saved_speed_index = use_state('saved-speed-index', 3) play_direction = saved_play_direction.value speed_index = saved_speed_index.value if play_direction == 0: frame_advance = 0 play_override = 0 def control(name: str, speed: int) -> None: nonlocal frame_advance, play_override x = input_down_gradual(name, 0.25) if x == 1.0: play_override = speed elif input_pressed(name): frame_advance += speed control('frame-next', 1) control('frame-next-alt', 1) control('frame-prev', -1) control('frame-prev-alt', -1) control('frame-next-fast', 10) control('frame-prev-fast', -10) if play_override != 0: if abs(play_override) in speed_options: speed_index = speed_options.index(abs(play_override)) else: speed_index = len(speed_options) - 1 play_direction = 1 if play_override > 0 else -1 else: self.model.selected_frame += frame_advance else: if input_down('frame-next') or input_down('frame-next-alt'): if play_direction == 1: speed_index += 1 else: play_direction = -play_direction elif input_down('frame-prev') or input_down('frame-prev-alt'): if play_direction == -1: speed_index += 1 else: play_direction = -play_direction elif input_down('frame-next-fast'): if play_direction == 1: speed_index += 2 else: play_direction = -play_direction speed_index += 1 elif input_down('frame-prev-fast'): if play_direction == -1: speed_index += 2 else: play_direction = -play_direction speed_index += 1 speed_index = min(max(speed_index, 0), len(speed_options) - 1) self.model.play_speed = play_direction * speed_options[speed_index] self.model.playback_mode = saved_play_direction.value != 0 def play_button(label: str, direction: int) -> None: disabled = play_direction == direction if ig.disableable_button(label, enabled=play_direction != direction): saved_play_direction.value = direction play_button('<|', -1) ig.same_line() play_button('||', 0) ig.same_line() play_button('|>', 1) ig.same_line() ig.push_item_width(63) changed, new_index = ig.combo( '##speed-option', speed_index, [str(s) + 'x' for s in speed_options], ) ig.pop_item_width() if changed: saved_speed_index.value = new_index if input_pressed('playback-play'): if saved_play_direction.value == 0: saved_play_direction.value = 1 else: saved_play_direction.value = 0 if input_pressed('playback-rewind'): if saved_play_direction.value == 0: saved_play_direction.value = -1 else: saved_play_direction.value = 0 if input_pressed('playback-speed-up'): saved_speed_index.value = min(saved_speed_index.value + 1, len(speed_options) - 1) if input_pressed('playback-slow-down'): saved_speed_index.value = max(saved_speed_index.value - 1, 0) ig.same_line() new_frame = ui.render_frame_slider( 'frame-slider', self.model.selected_frame, self.model.max_frame - 1, self.model.pipeline.cached_frames() if self.show_debug_pane else [], ) if new_frame is not None: self.model.selected_frame = new_frame.value
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_game_view_birds_eye( id: str, framebuffer_size: Tuple[int, int], model: Model, wall_hitbox_radius: float, hovered_surface: Optional[int], hidden_surfaces: Set[int], ) -> Optional[int]: ig.push_id(id) # TODO: Should zoom in on mouse when uncentered mouse_state = use_state('mouse-state', MouseTracker()).value zoom = use_state('zoom', -4.5) target: Ref[Optional[Tuple[float, float]]] = use_state('target', None) pos_y: Ref[Optional[float]] = use_state('pos-y', None) drag_amount = mouse_state.get_drag_amount() zoom.value += mouse_state.get_wheel_amount() / 5 world_span_x = 200 / math.pow(2, zoom.value) viewport = get_viewport(framebuffer_size) mario_pos = get_mario_pos(model) # Camera xz camera_xz = (mario_pos[0], mario_pos[2]) if target.value is not None: camera_xz = target.value if drag_amount != (0.0, 0.0): world_span_z = world_span_x * viewport.width / viewport.height if target.value is None: target.value = (mario_pos[0], mario_pos[2]) target.value = ( camera_xz[0] + drag_amount[1] * world_span_x / viewport.height, camera_xz[1] - drag_amount[0] * world_span_z / viewport.width, ) camera_xz = target.value if ig.disableable_button('Lock to Mario', enabled=target.value is not None): target.value = None # Camera y camera_y = mario_pos[1] + 500 if pos_y.value is None else pos_y.value ig.set_cursor_pos((viewport.width - 100, 10)) ig.begin_child('##y-slider') new_y, reset = render_pos_y_slider('y-slider', camera_y, mario_pos[1]) if reset: pos_y.value = None elif new_y is not None: pos_y.value = new_y camera_y = pos_y.value ig.end_child() camera = core.BirdsEyeCamera() camera.pos = (camera_xz[0], camera_y, camera_xz[1]) camera.span_y = world_span_x # Mouse xz mouse_world_pos = get_mouse_world_pos_birds_eye(camera) mouse_ray: Optional[Tuple[Vec3f, Vec3f]] if mouse_world_pos is not None: ig.set_cursor_pos((10, viewport.height - 25)) ig.text('(x, z) = (%.3f, %.3f)' % mouse_world_pos) mouse_ray = ((mouse_world_pos[0], camera.pos[1], mouse_world_pos[1]), (0, -1, 0)) else: mouse_ray = None if mouse_ray is None: new_hovered_surface = None else: new_hovered_surface = trace_ray(model, mouse_ray) render_game( model, viewport, camera, False, wall_hitbox_radius, hovered_surface=hovered_surface, hidden_surfaces=hidden_surfaces, ) ig.pop_id() return new_hovered_surface
def use_rotational_camera( framebuffer_size: Tuple[int, int], model: Model, ) -> Tuple[core.RotateCamera, bool]: mouse_state = use_state('mouse-state', MouseTracker()).value target: Ref[Optional[Vec3f]] = use_state('target', None) target_vel: Ref[Optional[Vec3f]] = use_state('target-vel', None) pitch = use_state('pitch', 0.0) yaw = use_state('yaw', 0.0) zoom = use_state('zoom', 0.0) prev_frame_time = use_state_with('prev-frame-time', time.time) lock_to_in_game = use_state('lock-to-in-game', False) delta_time = time.time() - prev_frame_time.value prev_frame_time.value = time.time() drag_amount = mouse_state.get_drag_amount() pitch.value -= drag_amount[1] / 200 yaw.value -= drag_amount[0] / 200 wheel_amount = mouse_state.get_wheel_amount() zoom.value += wheel_amount / 5 zoom.value = min(zoom.value, 7.0) mario_pos = get_mario_pos(model) target_pos = mario_pos if target.value is None else target.value fov_y = math.radians(45) if drag_amount != (0.0, 0.0) or wheel_amount != 0.0: lock_to_in_game.value = False if lock_to_in_game.value: target_pos = cast( Vec3f, model.get(model.selected_frame, 'gLakituState.focus')) target.value = target_pos camera_pos = cast(Vec3f, model.get(model.selected_frame, 'gLakituState.pos')) dpos = ( target_pos[0] - camera_pos[0], target_pos[1] - camera_pos[1], target_pos[2] - camera_pos[2], ) pitch.value, yaw.value = direction_to_angle(dpos) offset = math.sqrt(sum(c**2 for c in dpos)) if offset > 0.001: zoom.value = math.log(offset / 1500, 0.5) fov_y = math.radians( cast(float, model.get(model.selected_frame, 'sFOVState.fov'))) offset = 1500 * math.pow(0.5, zoom.value) face_direction = angle_to_direction(pitch.value, yaw.value) move = [0.0, 0.0, 0.0] # forward, up, right move[0] += input_float('3d-camera-move-f') move[0] -= input_float('3d-camera-move-b') move[1] += input_float('3d-camera-move-u') move[1] -= input_float('3d-camera-move-d') move[2] += input_float('3d-camera-move-r') move[2] -= input_float('3d-camera-move-l') if move != [0.0, 0.0, 0.0] or (target.value is not None and not lock_to_in_game.value): mag = math.sqrt(sum(c**2 for c in move)) if mag > 1: move = [c / mag for c in move] max_speed = 50.0 * delta_time * math.sqrt(offset) f = (math.sin(yaw.value), 0, math.cos(yaw.value)) u = (0, 1, 0) r = (-f[2], 0, f[0]) end_vel = cast( Vec3f, tuple(max_speed * move[0] * f[i] + max_speed * move[1] * u[i] + max_speed * move[2] * r[i] for i in range(3))) accel = 10.0 * delta_time * math.sqrt(offset) current_vel = target_vel.value or (0.0, 0.0, 0.0) target_vel.value = move_toward(current_vel, end_vel, accel) target.value = ( target_pos[0] + target_vel.value[0], target_pos[1] + target_vel.value[1], target_pos[2] + target_vel.value[2], ) target_pos = target.value lock_to_in_game.value = False if ig.disableable_button('Lock to Mario', enabled=target.value is not None): target.value = None target_vel.value = None lock_to_in_game.value = False ig.same_line() if ig.disableable_button('Lakitu', enabled=not lock_to_in_game.value): lock_to_in_game.value = True camera_pos = ( target_pos[0] - offset * face_direction[0], target_pos[1] - offset * face_direction[1], target_pos[2] - offset * face_direction[2], ) camera = core.RotateCamera() camera.pos = camera_pos camera.target = target_pos camera.fov_y = fov_y show_camera_target = target.value is not None and not lock_to_in_game.value return camera, show_camera_target
def render_frame_log_tab(self) -> None: frame_offset = use_state('frame-offset', 1) round_numbers = use_state('round-numbers', True) ig.push_item_width(210) _, frame_offset.value = ig.combo( '##frame-offset', frame_offset.value, ['previous -> current frame', 'current -> next frame'], ) ig.pop_item_width() _, round_numbers.value = ig.checkbox('Round##round-numbers', round_numbers.value) ig.dummy(1, 10) events = self.model.pipeline.frame_log(self.model.selected_frame + frame_offset.value) def string(addr: object) -> str: return self.model.pipeline.read_string(0, dcast(Address, addr)).decode('utf-8') def f32(number: object) -> str: assert isinstance(number, float) if round_numbers.value: return '%.3f' % number else: return str(number) def vec3f(vector: object) -> str: return '(' + ', '.join(map(f32, dcast(list, vector))) + ')' def action(action: object) -> str: return self.model.action_names[dcast(int, action)] indent = 0 action_indent = 0 def show_text(text: str) -> None: ig.text(' ' * indent + text) for event in events: if event['type'] == 'FLT_CHANGE_ACTION': show_text( f'change action: {action(event["from"])} -> {action(event["to"])}' ) elif event['type'] == 'FLT_CHANGE_FORWARD_VEL': show_text( f'change f vel: {f32(event["from"])} -> {f32(event["to"])} ({string(event["reason"])})' ) elif event['type'] == 'FLT_WALL_PUSH': show_text( f'wall push: {vec3f(event["from"])} -> {vec3f(event["to"])} (surface {event["surface"]})' ) elif event['type'] == 'FLT_BEGIN_MOVEMENT_STEP': type_ = {1: 'air', 2: 'ground', 3: 'water'}[event['stepType']] show_text(f'{type_} step {event["stepNum"]}:') indent += 1 elif event['type'] == 'FLT_END_MOVEMENT_STEP': indent -= 1 elif event['type'] == 'FLT_EXECUTE_ACTION': indent -= action_indent action_indent = 0 show_text(f'execute action: {action(event["action"])}') indent += 1 action_indent += 1 else: sorted_event = {'type': event['type']} sorted_event.update(sorted(event.items())) show_text(str(sorted_event))
def render_intended_stick_control(self, id: str) -> None: up_options = ['3d view', 'mario yaw', 'stick y', 'world x'] up_option = use_state('up-option', 0) ig.text('up =') ig.same_line() ig.push_item_width(100) _, up_option.value = ig.combo('##up-option', up_option.value, up_options) ig.pop_item_width() ig.dummy(1, 10) stick_x_var = Variable('input-stick-x').with_frame( self.model.selected_frame) stick_y_var = Variable('input-stick-y').with_frame( self.model.selected_frame) face_yaw = dcast( int, self.model.get( Variable('mario-face-yaw').with_frame( self.model.selected_frame))) camera_yaw = dcast( int, self.model.get( Variable('camera-yaw').with_frame(self.model.selected_frame)) or 0) squish_timer = dcast( int, self.model.get(self.model.selected_frame, 'gMarioState->squishTimer')) active_face_yaw = face_yaw events = self.model.pipeline.frame_log(self.model.selected_frame + 1) active_face_yaw_action = None for event in events: if event['type'] == 'FLT_EXECUTE_ACTION': action_name = self.model.action_names[event['action']] active_face_yaw = event['faceAngle'][1] active_face_yaw_action = action_name if action_name == 'idle': break up_angle = { 'mario yaw': active_face_yaw, 'stick y': camera_yaw + 0x8000, 'world x': 0x4000, '3d view': self.model.rotational_camera_yaw, }[up_options[up_option.value]] self.model.input_up_yaw = up_angle raw_stick_x = dcast(int, self.model.get(stick_x_var)) raw_stick_y = dcast(int, self.model.get(stick_y_var)) adjusted = stick_raw_to_adjusted(raw_stick_x, raw_stick_y) intended = stick_adjusted_to_intended( adjusted, face_yaw, camera_yaw, squish_timer != 0, ) def render_value(label: str, value: object, formatter: VariableFormatter) -> Optional[Any]: label_width = 60 value_size = ( 60 if label == 'dyaw' else 80, ig.get_text_line_height() + 2 * ig.get_style().frame_padding[1], ) ig.push_item_width(label_width) ig.selectable(label, width=label_width) ig.pop_item_width() ig.same_line() new_value, _, _ = ui.render_variable_value('value-' + label, value, formatter, value_size) return None if new_value is None else new_value.value target_yaw: Optional[int] = None target_dyaw: Optional[int] = None target_mag: Optional[float] = None target_mag = render_value('int mag', intended.mag, FloatFormatter()) target_yaw = render_value('int yaw', intended.yaw, DecimalIntFormatter()) dyaw = intended.yaw - active_face_yaw target_dyaw = render_value('dyaw', dyaw, DecimalIntFormatter()) ig.same_line() if ig.button('?'): ig.open_popup('active-yaw-expl') if ig.begin_popup('active-yaw-expl'): ig.text(f'{intended.yaw} - {active_face_yaw} = {dyaw}') ig.text(f'intended yaw = {intended.yaw}') if active_face_yaw == face_yaw: ig.text(f'face yaw = {face_yaw}') if active_face_yaw != face_yaw: ig.text( f'face yaw = {active_face_yaw} at start of {active_face_yaw_action} action' ) ig.text(f'(face yaw = {face_yaw} at start of frame)') ig.end_popup() if dyaw not in range(0, 16): if ig.button('dyaw = 0'): target_dyaw = 0 if target_yaw is not None or target_dyaw is not None or target_mag is not None: relative_to = 0 if target_yaw is not None else active_face_yaw if target_dyaw is not None: target_yaw = active_face_yaw + target_dyaw if target_yaw is None: target_yaw = intended.yaw if target_mag is None: target_mag = intended.mag new_raw_stick_x, new_raw_stick_y = intended_to_raw( face_yaw, camera_yaw, squish_timer, target_yaw, target_mag, relative_to) self.model.set(stick_x_var, new_raw_stick_x) self.model.set(stick_y_var, new_raw_stick_y) n_a = intended.yaw - up_angle n_x = intended.mag / 32 * math.sin(-n_a * math.pi / 0x8000) n_y = intended.mag / 32 * math.cos(n_a * math.pi / 0x8000) ig.set_cursor_pos((ig.get_cursor_pos().x + 155, 0)) new_n = ui.render_joystick_control(id, n_x, n_y, 'circle') if new_n is not None: new_n_a = int(math.atan2(-new_n[0], new_n[1]) * 0x8000 / math.pi) new_intended_yaw = up_angle + new_n_a new_intended_mag = 32 * math.sqrt(new_n[0]**2 + new_n[1]**2) new_raw_stick_x, new_raw_stick_y = intended_to_raw( face_yaw, camera_yaw, squish_timer, new_intended_yaw, new_intended_mag, relative_to=0) self.model.set(stick_x_var, new_raw_stick_x) self.model.set(stick_y_var, new_raw_stick_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_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, )