def render_object_slots( id: str, behaviors: List[Optional[ObjectBehavior]], behavior_name: Callable[[ObjectBehavior], str], ) -> Optional[int]: ig.push_id(id) button_size = 50 window_left = ig.get_window_position()[0] window_right = window_left + ig.get_window_content_region_max()[0] prev_item_right = window_left style = ig.get_style() result = None for slot, behavior in enumerate(behaviors): item_right = prev_item_right + style.item_spacing[0] + button_size if item_right > window_right: prev_item_right = window_left elif slot != 0: ig.same_line() prev_item_right = prev_item_right + style.item_spacing[0] + button_size if behavior is None: label = str(slot) else: label = str(slot) + '\n' + behavior_name(behavior) if ig.button(label + '##slot-' + str(slot), button_size, button_size): result = slot ig.pop_id() return result
def render_pos_y_slider( id: str, pos_y: float, mario_pos_y: float, ) -> Tuple[Optional[float], bool]: ig.push_id(id) ig.text('max y = %.f' % pos_y) ig.set_cursor_pos((ig.get_window_width() - 30, ig.get_cursor_pos().y)) slider_pos = ig.get_cursor_pos() slider_width = 20 slider_height = ig.get_content_region_available().y slider_range = range(-8191, 8192) mario_icon_x = ig.get_cursor_pos().x t = (mario_pos_y - slider_range.start) / len(slider_range) mario_icon_y = ig.get_cursor_pos().y + (1 - t) * slider_height ig.set_cursor_pos((mario_icon_x, mario_icon_y)) reset = ig.button('M', width=slider_width) ig.set_cursor_pos(slider_pos) changed, value = ig.v_slider_float( '##slider', width=slider_width, height=slider_height, value=pos_y, min_value=slider_range.start, max_value=slider_range.stop - 1, format='', ) new_y = value if changed else None ig.pop_id() return new_y, reset
def render(id: str) -> None: nonlocal error if error is not None: message = error.strip() ig.text('Wafel has crashed. Cause:') lines = message.split('\n') ig.input_text_multiline( '##error-msg', message, len(message) + 1, max(map(len, lines)) * 10, (len(lines) + 1) * ig.get_text_line_height() + 6, ) ig.dummy(10, 10) if ig.button('Exit'): log.info('Aborted') sys.exit(1) ig.same_line() if ig.button('Try to save'): if view.ask_save_filename(): view.save() # ig.same_line() # if view is not None and ig.button('Try to continue (mad lads only)'): # view.reload_ui() # error = None return try: if hasattr(model, 'pipeline'): log.timer.get_num_copies = lambda: model.pipeline.num_copies( ) if config.dev_mode else 0 log.timer.get_num_updates = lambda: model.pipeline.num_advances( ) if config.dev_mode else 0 log.timer.begin_frame() ig.try_render(lambda: do_render(id)) except: error = traceback.format_exc() log.error('Caught: ' + error) finally: log.timer.end_frame()
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_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_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