Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
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))
Ejemplo n.º 3
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()
Ejemplo n.º 5
0
    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()
Ejemplo n.º 6
0
    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()
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
    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()
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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()
Ejemplo n.º 11
0
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