def render_unsaved_exit(window): "Popup to prevent exiting the application with unsaved work." if window.exit_unsaved_drawings: imgui.open_popup("Really exit?") imgui.set_next_window_size(500, 200) if imgui.begin_popup_modal("Really exit?")[0]: imgui.text("You have unsaved work in these drawing(s):") imgui.begin_child("unsaved", border=True, height=imgui.get_content_region_available()[1] - 26) for drawing in window.exit_unsaved_drawings: imgui.text(drawing.filename) if imgui.is_item_hovered(): pass # TODO popup thumbnail of the picture? imgui.end_child() if imgui.button("Yes, exit anyway"): imgui.close_current_popup() pyglet.app.exit() imgui.same_line() if imgui.button("Yes, but save first"): for drawing in window.exit_unsaved_drawings: window.save_drawing(drawing) pyglet.app.exit() imgui.same_line() if imgui.button("No, cancel"): window.exit_unsaved_drawings = None imgui.close_current_popup() imgui.end_popup()
async def show_pleple_window(name, plot_info, multi_resolution_signals, plot_gl_resources, plot_gl_canvas) -> IMGui[None]: with window(name): window_width, window_height = im.get_content_region_available() window_width, window_height = int(window_width), int(window_height) if (window_width, window_height) != (plot_gl_canvas.width_px, plot_gl_canvas.height_px): print("\t### resizing canvas {} -> {}".format( (plot_gl_canvas.width_px, plot_gl_canvas.height_px), (window_width, window_height)), flush=True) del plot_gl_canvas plot_gl_canvas = aplt.init_plot_canvas(window_width, window_height) await emit(___new_canvas_(plot_gl_canvas)) canvas_width = plot_gl_canvas.width_px canvas_height = plot_gl_canvas.height_px should_redraw = True if should_redraw: aplt.draw_plot_to_canvas_multires( plot_gl_canvas, plot_info, multi_resolution_signals, plot_gl_resources, line_color=aplt.WHITE, background_color=aplt.TRANSPARENT_BLACK) im.image(plot_gl_canvas.texture_id, canvas_width, canvas_height, uv0=(0, 1), uv1=(1, 0))
async def signal_plot_window( plot_box_state: PlotBoxState, m_inputs: List[Maybe[Signal]], ui_settings: Dict[str, Any]) -> Eff[[ACTIONS], IMGui[None]]: m_signal = m_inputs[0] assert ((type(m_signal.val) == Signal) if m_signal.is_Just() else True), repr(m_signal) PLOT_WINDOW_FLAGS = 0 if ui_settings[ 'plot_window_movable'] else im.WINDOW_NO_MOVE plot_name = "Plot (id={id})###{id}".format(id=plot_box_state.id_) with window(name=plot_name, flags=PLOT_WINDOW_FLAGS): plot_width = -1. # auto plot_height = im.get_content_region_available().y - 20 await signal_plot(plot_box_state, m_signal, width=plot_width, height=plot_height, ui_settings=ui_settings) if m_signal.is_Nothing(): im.text(" ") elif m_signal.is_Just(): signal = m_signal.val im.text_colored(str(signal), 0.8, 0.8, 0.8) return get_window_rect()
def render ( self ): super().render() if imgui.begin( "Music Editor" ): ( width, height ) = imgui.get_content_region_available(); ( changed, value ) = imgui.input_text_multiline( "###Code", self.code, 1000, width = width, height = height / 2 ) if changed: self.code = value to_parse = False to_play = False if imgui.button( "Parse" ): to_parse = True imgui.same_line() if imgui.button( "Play" ): to_play = True try: if to_parse or to_play: self.player = Player() self.player.sequencers.append( FluidSynthSequencer() ) parser = Parser(); self.parsedTree = parser.parse( self.code ) self.context = self.create_context( self.player ) value = self.parsedTree.eval( self.context ) self.player.events = events = list( value ) if isinstance( value, Music ) else [] self.parsedException = None if to_play: self.player.play() except Exception as ex: self.parsedException = ex print(ex) traceback.print_tb(ex.__traceback__) if self.parsedException != None: imgui.text_colored( str( self.parsedException ), 1, 0, 0 ) self.expressionTab = self.imgui_tabbar( self.expressionTab, [ ( EXPRESSION_TAB_AST, "AST", self.render_inspector_ast ), ( EXPRESSION_TAB_EVENTS, "Events", self.render_inspector_events ), ( EXPRESSION_TAB_KEYBOARD, "Keyboard", self.render_inspector_keyboard ) ] ) imgui.end()
def show(self, value, read_only): clicked, self.show_texture = imgui.checkbox("Show", self.show_texture) if not self.show_texture: return texture = value.value if texture is None: self.texture_aspect = None else: h, w, _ = texture.shape self.texture_aspect = w / h cursor_pos = imgui.get_cursor_screen_pos() imgui.set_next_window_size(500, 500, imgui.ONCE) imgui.set_next_window_size_constraints((0.0, 0.0), (float("inf"), float("inf")), self.window_size_callback) imgui.set_next_window_position(*imgui.get_io().mouse_pos, imgui.ONCE, pivot_x=0.5, pivot_y=0.5) expanded, opened = imgui.begin( "Texture of %s###%s%s" % (self.node.spec.name, id(self.node), id(self)), True, imgui.WINDOW_NO_SCROLLBAR) if not opened: self.show_texture = False if expanded: if texture is None: imgui.text("No texture associated") else: # [::-1] reverses list texture_size = texture.shape[:2][::-1] texture_aspect = texture_size[0] / texture_size[1] window_size = imgui.get_content_region_available() imgui.image(texture._handle, window_size[0], window_size[0] / texture_aspect) if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): # little hack to have first context menu entry under cursor io = imgui.get_io() pos = io.mouse_pos io.mouse_pos = pos[0] - 20, pos[1] - 20 imgui.open_popup("context") io.mouse_pos = pos if imgui.begin_popup("context"): if imgui.menu_item("save")[0]: util.image.save_screenshot(texture.get()) imgui.menu_item("texture handle: %s" % texture._handle, None, False, False) imgui.menu_item("texture dtype: %s" % str(texture.dtype), None, False, False) imgui.menu_item("texture shape: %s" % str(texture.shape), None, False, False) imgui.end_popup() imgui.end()
def draw(self): imgui.begin("Scope") self.begin_input(self.input) imgui.button('input') self.end_input() imgui.same_line(spacing=16) imgui.plot_lines("Sin(t)", np.array(self.values).astype(np.float32), graph_size=imgui.get_content_region_available()) imgui.end()
def show_imgui_plot( plot_box_state: PlotState, signal: Signal, width: int = -1, height: int = -1, ui_settings={}, ) -> IMGui[None]: debug_log_time(SIGNAL_PLOT_CALL_START) time_range = plot_box_state.plot_state.time_range if width == -1: width = int(im.get_content_region_available().x) if height == -1: height = int(im.get_content_region_available().y) debug_log_time(DATA_GET_START) data_part = slice_signal( signal, time_range, n_points_needed=width, variant=ui_settings['plot_resample_function']).astype( np.dtype('float32')) debug_log_time(DATA_GET_END) debug_log_time(WAVE_DRAW_START) im.plot_lines( "the_actual_plot##{}".format(plot_box_state.id_), values=data_part, graph_size=(width, height), scale_min=signal.physical_min, scale_max=signal.physical_max, ) debug_log_time(WAVE_DRAW_END) debug_log_time(SIGNAL_PLOT_CALL_END)
def render_map(self): imgui.begin("map") imgui.slider_float("x (m)", self.track[self.i, 0] * ceiltrack.CEIL_HEIGHT, -80, 80) imgui.slider_float("y (m)", self.track[self.i, 1] * ceiltrack.CEIL_HEIGHT, -80, 80) imgui.slider_float("theta", self.track[self.i, 2] % (np.pi * 2), -7, 7) imgui.slider_float("x (grid)", self.track[self.i, 0] / ceiltrack.X_GRID, -10, 10) imgui.slider_float("y (grid)", self.track[self.i, 1] / ceiltrack.X_GRID, -10, 10) changed, self.ceilheight = imgui.slider_float("ceiling height (m)", self.ceilheight, 2, 4) if changed: self.loadframe(self.i) dl = imgui.get_window_draw_list() pos = imgui.get_cursor_screen_pos() siz = imgui.get_content_region_available() if siz[1] == 0: siz = [400, 300] # just use a fixed size w = siz[0] imgui.image_button(self.floortex, w, w / 2, frame_padding=0) # imgui.image_button(self.floortex, siz[0], siz[0]) origin = [pos[0], pos[1]] scale = 50 * ceiltrack.CEIL_HEIGHT * w / 1000 trackcolor = imgui.get_color_u32_rgba(0.3, 0.5, 0.3, 1) for i in range(1, self.i): dl.add_line(origin[0] + scale * self.track[i - 1, 0], origin[1] + scale * self.track[i - 1, 1], origin[0] + scale * self.track[i, 0], origin[1] + scale * self.track[i, 1], trackcolor, 1.5) carcolor = imgui.get_color_u32_rgba(0, 1, 0.6, 1) B = self.track[self.i] dl.add_line(origin[0] + scale * B[0], origin[1] + scale * B[1], origin[0] + scale * (B[0] + np.cos(B[2])), origin[1] + scale * (B[1] - np.sin(B[2])), carcolor, 1.5) imgui.end()
def render(self, play): imgui.begin("sim") dl = imgui.get_window_draw_list() meterwidth = max(np.max(self.track[:, 0]), np.max(self.lm[:, 0])) if play: if imgui.button("pause"): play = False else: if imgui.button("play"): play = True # sliders _, xy = imgui.slider_float2("car xy", self.car.x, -self.car.y, 0, meterwidth) self.car.x, self.car.y = xy[0], -xy[1] _, self.car.theta = imgui.slider_float("car theta", self.car.theta, 0, 2 * np.pi) imgui.slider_float("car v", self.car.v, 0, 10) imgui.slider_float2("controls", self.u, self.k, -1, 1) _, self.lanewidth = imgui.slider_float("lane width", self.lanewidth, 0.1, 2.0) # render overview pos = imgui.get_cursor_screen_pos() siz = imgui.get_content_region_available() if siz[1] <= 0: siz = [400, 400] imgui.invisible_button("overview", siz[0], siz[1]) origin = pos scale = siz[0] / (meterwidth * 1.1) conecolor = imgui.get_color_u32_rgba(1, 0.7, 0, 1) for lm in self.lm: dl.add_rect_filled(origin[0] + lm[0] * scale - 2, origin[1] - lm[1] * scale - 2, origin[0] + lm[0] * scale + 2, origin[1] - lm[1] * scale + 2, conecolor, 3) trackcolor = imgui.get_color_u32_rgba(1, 1, 0.3, 0.5) for i in range(0, len(self.track), 2): j = (i + 1) % len(self.track) ti = self.track[i] * scale tj = self.track[j] * scale dl.add_line(origin[0] + ti[0], origin[1] - ti[1], origin[0] + tj[0], origin[1] - tj[1], trackcolor, 1.5) for i in range(0, len(self.track)): j = (i + 1) % len(self.track) ti = self.track[i] * scale tj = self.track[j] * scale Li = ti[2:4] * self.lanewidth Lj = tj[2:4] * self.lanewidth dl.add_line(origin[0] + ti[0] + Li[0], origin[1] - ti[1] - Li[1], origin[0] + tj[0] + Lj[0], origin[1] - tj[1] - Lj[1], trackcolor, 3) dl.add_line(origin[0] + ti[0] - Li[0], origin[1] - ti[1] + Li[1], origin[0] + tj[0] - Lj[0], origin[1] - tj[1] + Lj[1], trackcolor, 3) mx = scale * self.car.x my = scale * self.car.y # draw forward projections for traj in self.shots: for i in range(len(traj) - 1): dl.add_line(origin[0] + traj[i, 0] * scale, origin[1] - traj[i, 1] * scale, origin[0] + traj[i + 1, 0] * scale, origin[1] - traj[i + 1, 1] * scale, imgui.get_color_u32_rgba(0, 1, 1, 1), 1) # draw car + velocity vxy = scale * self.car.v * .1 * \ np.array([np.cos(self.car.theta), np.sin(self.car.theta)]) dl.add_line(origin[0] + mx, origin[1] - my, origin[0] + mx + vxy[0], origin[1] - my - vxy[1], imgui.get_color_u32_rgba(0, 1, 0, 1), 1) imgui.end() return play
def render_graphs(self): imgui.begin("graphs") i = self.i dl = imgui.get_window_draw_list() mi = max(0, i - 30 * 3) temp = self.controlstate[mi:i + 1, 3].copy() imgui.plot_lines("velocity", temp) temp = self.controlstate[mi:i + 1, 8].copy() imgui.plot_lines("target v", temp) temp = self.controls[mi:i + 1, 0].copy() imgui.plot_lines("control v", temp) temp = self.controls[mi:i + 1, 1].copy() imgui.plot_lines("control steer", temp) temp = self.controlstate[mi:i + 1, 9].copy() imgui.plot_lines("target w", temp) temp = self.controlstate[mi:i + 1, 4].copy() imgui.plot_lines("yaw rate", temp) # live variables maxv = int(np.ceil(np.max(self.controlstate[:, 3]) * 1.1)) imgui.slider_float("wheel v", self.controlstate[i, 3], 0, maxv) imgui.slider_float("target v", self.controlstate[i, 8], 0, maxv) lv = 0 if i > 0: dx = self.controlstate[i, 0] - self.controlstate[i - 1, 0] dy = self.controlstate[i, 1] - self.controlstate[i - 1, 1] lv = np.sqrt(dx**2 + dy**2) * 30.0 imgui.slider_float("localized v", lv, 0, maxv) imgui.slider_float("control motor", self.controls[i, 0] / 127., -1, 1) imgui.slider_float("control steer", self.controls[i, 1] / 127., -1, 1) # for yaw rate and curvature, set the limits backwards # so that turning right is to the right maxw = int(np.ceil(np.max(np.abs(self.controlstate[:, 4])) * 1.1)) imgui.slider_float("yaw rate", self.controlstate[i, 4], maxw, -maxw) imgui.slider_float("target w", self.controlstate[i, 9], maxw, -maxw) v = self.controlstate[i, 3] if v > 0.5: k = self.controlstate[i, 4] / v else: k = 0 imgui.slider_float("curvature", k, 2, -2) targetK = self.controlstate[self.i, 7] imgui.slider_float("target k", targetK, 2, -2) # render overview pos = imgui.get_cursor_screen_pos() siz = imgui.get_content_region_available() if siz[1] == 0: siz = [400, 300] imgui.invisible_button("overview", siz[0], siz[0] * 0.7) origin = pos meterwidth = max(np.max(self.track[:, 0]), np.max(self.lm[:, 0])) scale = siz[0] / (meterwidth * 1.1) conecolor = imgui.get_color_u32_rgba(1, 0.7, 0, 1) for lm in self.lm: dl.add_rect_filled(origin[0] + lm[0] * scale - 2, origin[1] - lm[1] * scale - 2, origin[0] + lm[0] * scale + 2, origin[1] - lm[1] * scale + 2, conecolor, 3) trackcolor = imgui.get_color_u32_rgba(1, 1, 0.3, 0.5) for i in range(0, len(self.track), 2): j = (i + 1) % len(self.track) ti = self.track[i] * scale tj = self.track[j] * scale dl.add_line(origin[0] + ti[0], origin[1] - ti[1], origin[0] + tj[0], origin[1] - tj[1], trackcolor, 1.5) particlecolor = imgui.get_color_u32_rgba(1, 1, 1, 0.3) dl.add_rect(pos[0], pos[1], pos[0] + siz[0], pos[1] + siz[1], particlecolor, 1) if 'particles' in self.frame: ps = self.frame['particles'] for p in ps: dl.add_rect_filled(origin[0] + p[0] * scale, origin[1] - p[1] * scale, origin[0] + p[0] * scale + 1, origin[1] - p[1] * scale + 1, particlecolor) # also draw a mean velocity vector mxy = scale * np.mean(ps[:, :2], axis=0) vxy = scale * v * .1 * np.array( [np.mean(np.cos(ps[:, 3])), np.mean(np.sin(ps[:, 3]))]) dl.add_line(origin[0] + mxy[0], origin[1] - mxy[1], origin[0] + mxy[0] + vxy[0], origin[1] - mxy[1] - vxy[1], imgui.get_color_u32_rgba(0, 1, 0, 1), 1) else: x = self.controlstate[self.i, 0] y = self.controlstate[self.i, 1] theta = self.controlstate[self.i, 2] imgui.slider_float("x", x, 0, 20) imgui.slider_float("y", y, -10, 0) imgui.slider_float("theta", theta % (2 * np.pi), -np.pi, np.pi) v = 0.3 * self.controlstate[self.i, 3] S, C = np.sin(theta), np.cos(theta) dl.add_rect_filled(origin[0] + x * scale - 3, origin[1] - y * scale - 3, origin[0] + scale * x + 3, origin[1] - scale * y + 3, imgui.get_color_u32_rgba(0, 1, 0, 1), 1) dl.add_line(origin[0] + x * scale, origin[1] - y * scale, origin[0] + scale * (x + v * C), origin[1] - scale * (y + v * S), imgui.get_color_u32_rgba(0, 1, 0, 1), 1) targetaccel = self.controlstate[self.i][12:14] / 9.8 accel = self.carstate[self.i][2] ox, oy = origin[0] + scale * 3, origin[1] + scale * 9 for i in range(100): t0 = i * 2 * np.pi / 100 t1 = (i + 1) * 2 * np.pi / 100 dl.add_line(ox + 2 * scale * np.cos(t0), oy - 2 * scale * np.sin(t0), ox + 2 * scale * np.cos(t1), oy - 2 * scale * np.sin(t1), imgui.get_color_u32_rgba(0.7, 0.7, 0.7, 1), 1) dl.add_line(ox + scale * np.cos(t0), oy - scale * np.sin(t0), ox + scale * np.cos(t1), oy - scale * np.sin(t1), imgui.get_color_u32_rgba(0.7, 0.7, 0.7, 1), 1) dl.add_line(ox, oy, ox + scale * accel[1], oy - scale * accel[0], imgui.get_color_u32_rgba(0.3, 1, 0.5, 1), 3) dl.add_rect(ox - scale * targetaccel[1] - 2, oy + scale * targetaccel[0] - 2, ox - scale * targetaccel[1] + 2, oy + scale * targetaccel[0] + 2, imgui.get_color_u32_rgba(0.0, 1, 1, 1), 1) imgui.end()
def pan_zoom(name, state, width=None, height=None, content_gen=None, drag_tag=None, down_tag=None, hover_tag=None): """ Create the Pan & Zoom widget, serving as a container for a thing given by `content_gen`. This widget is mostly not used directly. Instead, a special-purpose wrapper can be used, such as `concur.extra_widgets.image.image`, or `concur.extra_widgets.frame.frame`. Args: name: widget name. Also serves as an event identifier state: `PanZoom` object representing state. width: widget width in pixels. If `None`, it fills the available space. height: widget height in pixels. If `None`, it fills the available space. content_gen: Widget generator to display inside the pan-zoom widget. All events are passed through as a return tuple member. This is a function that returns a Concur widget, and takes two keyword arguments: * **tf**: `concur.extra_widgets.pan_zoom.TF` object with transformation information * **event_gen**: This is an event generator which returns the drag, down, and hover events when they occur. It is up to the widget to do the necessary transformations using the `TF` object, and optionally react to the events created by `event_gen`. drag_tag: The event tag for LMB drag events. If `None`, no drag events are fired. down_tag: The event tag for LMB down events. If `None`, no down events are fired. Useful in combination with `drag_tag` to indicate when the dragging started. hover_tag: The event tag for mouse hover when no mouse buttons are down. If `None`, no hover events are fired. Events are fired only when the mouse is moving. Returns: To ease integration with other widgets, this widget returns events in a special format, `(name, state, event)`. `state` or `event` may be `None` in case `state` didn't change, or there is no `event`. If `drag_tag`, or `down_tag` arguments are used, these are fired in the `event` member of the return tuple. """ assert content_gen is not None tf, content = None, None event_queue = queue.Queue() while True: assert not state.keep_aspect or not state.fix_axis, \ "Can't fix axis and keep_aspect at the same time." # Dynamically rescale width and height if they weren't specified if width is None: w = imgui.get_content_region_available()[0] else: w = width if height is None: h = imgui.get_content_region_available()[1] else: h = height frame_w = max(1, w - state.margins[0] + state.margins[2]) frame_h = max(1, h - state.margins[1] + state.margins[3]) w = max(1, w) h = max(1, h) zoom_x = frame_w / (state.right - state.left) zoom_y = frame_h / (state.bottom - state.top) left, right = state.left, state.right top, bottom = state.top, state.bottom if state.keep_aspect: aspect = float(state.keep_aspect) assert state.keep_aspect > 0, "Negative aspect ratio is not supported." if abs(zoom_x) > abs(zoom_y) * aspect: zoom_x = np.sign(zoom_x) * abs(zoom_y) * aspect center_x = (left + right) / 2 left = center_x - frame_w / zoom_x / 2 right = center_x + frame_w / zoom_x / 2 if abs(zoom_y) > abs(zoom_x) / aspect: zoom_y = np.sign(zoom_y) * abs(zoom_x) / aspect center_y = (top + bottom) / 2 top = center_y - frame_h / zoom_y / 2 bottom = center_y + frame_h / zoom_y / 2 origin = imgui.get_cursor_screen_pos() # needs to be before is_window_hovered imgui.begin_child("Pan-zoom", w, h, False, flags=imgui.WINDOW_NO_SCROLLBAR | imgui.WINDOW_NO_SCROLL_WITH_MOUSE) # Interaction st = copy.deepcopy(state) io = imgui.get_io() is_window_hovered = imgui.is_window_hovered() if (imgui.is_mouse_clicked(1) or imgui.is_mouse_clicked(2)) and is_window_hovered: st.is_rmb_dragged = True if not (io.mouse_down[1] or io.mouse_down[2]): st.is_rmb_dragged = False delta = io.mouse_delta # Pan if st.is_rmb_dragged and (delta[0] or delta[1]): if st.fix_axis != 'x': st.left -= delta[0] / zoom_x st.right -= delta[0] / zoom_x if st.fix_axis != 'y': st.top -= delta[1] / zoom_y st.bottom -= delta[1] / zoom_y # Zoom if (imgui.is_window_hovered() or st.is_rmb_dragged) and io.mouse_wheel: factor = 1.3**io.mouse_wheel if st.fix_axis != 'x': mx_rel = (io.mouse_pos[0] - origin[0] - state.margins[0]) / frame_w * 2 - 1 mx = mx_rel * (right - left) / (st.right - st.left) / 2 + 0.5 wi = st.right - st.left st.left = st.left + wi * mx - wi / factor * mx st.right = st.right - wi * (1 - mx) + wi / factor * (1 - mx) if st.fix_axis != 'y': my_rel = (io.mouse_pos[1] - origin[1] - state.margins[1]) / frame_h * 2 - 1 my = my_rel * (bottom - top) / (st.bottom - st.top) / 2 + 0.5 hi = st.bottom - st.top st.top = st.top + hi * my - hi / factor * my st.bottom = st.bottom - hi * (1 - my) + hi / factor * (1 - my) # Get screen-space bounds disregarding the margins left_s = left - st.margins[0] / zoom_x top_s = top - st.margins[1] / zoom_y right_s = right - st.margins[2] / zoom_x bottom_s = bottom - st.margins[3] / zoom_y s2c = np.array([ # Screen to Content [1 / zoom_x, 0, left_s - origin[0] / zoom_x], [0, 1 / zoom_y, top_s - origin[1] / zoom_y] ]) c2s = np.array([ # Content to Screen [zoom_x, 0, origin[0] - left_s * zoom_x], [0, zoom_y, origin[1] - top_s * zoom_y] ]) view_s = [origin[0], origin[1], origin[0] + w, origin[1] + h] view_c = [left_s, top_s, right_s, bottom_s] content_value = None content_returned = False new_tf = TF(c2s, s2c, view_c, view_s) if down_tag \ and not st.is_rmb_dragged \ and imgui.is_mouse_clicked() \ and is_window_hovered: event_queue.put( (down_tag, new_tf.inv_transform(np.array([[*io.mouse_pos] ]))[0])) if hover_tag \ and not st.is_rmb_dragged \ and not any(imgui.is_mouse_down(i) for i in [0,1,2]) \ and is_window_hovered: new_tf.hovered = True if (delta[0] or delta[1]): event_queue.put( (hover_tag, new_tf.inv_transform(np.array([[*io.mouse_pos]]))[0])) if drag_tag \ and not st.is_rmb_dragged: imgui.invisible_button("", w, h) dx, dy = io.mouse_delta if imgui.is_item_active() and (dx or dy): event_queue.put( (drag_tag, np.array([new_tf.s2c[0, 0] * dx, new_tf.s2c[1, 1] * dy]))) imgui.set_item_allow_overlap() try: from concur.core import lift if tf is None or tf != new_tf: tf = new_tf w, h = tf.view_s[2] - tf.view_s[0], tf.view_s[3] - tf.view_s[1] content = content_gen(tf=tf, event_gen=lambda: listen(event_queue)) next(content) except StopIteration as e: content_value = e.value content_returned = True finally: imgui.end_child() changed = st != state if changed or content_returned: return name, (st if changed else None, content_value) yield