Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/src/_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ CurrentModule = CImGui
This documents notable changes in CImGui.jl. The format is based on [Keep a
Changelog](https://keepachangelog.com).

## Unreleased

### Changed
- Modified [`MakieFigure`](@ref) to not rely on the `id` for its internal state
so that it's safe to call it on different figures with the same ID (not at
the same time of course). Also implemented [`delete_figure!()`](@ref) to allow
for cleaning up the internal state.

## [v6.2.0] - 2025-09-27

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ imgui_version
```@autodocs
Modules = [CImGui]
Order = [:constant, :function, :type]
Filter = t -> nameof(t) ∉ (:imgui_version, :render, :set_backend, :MakieFigure)
Filter = t -> nameof(t) ∉ (:imgui_version, :render, :set_backend, :MakieFigure, :delete_figure!)
```
1 change: 1 addition & 0 deletions docs/src/makie.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ None of this is thread-safe. Here's what you can and can't do:

```@docs
MakieFigure
delete_figure!
```
8 changes: 7 additions & 1 deletion examples/makie_demo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ function makie_demo(; engine=nothing, spawn=1)
@c ig.Checkbox("Stream data", &stream_data)

if stream_data && isnothing(data_task)
data_task = errormonitor(Threads.@spawn live_data(data))
# We have to launch the task on the same thread because it modifies
# observables, which will end up modifying thread-unsafe OpenGL
# things. Hence we use @async instead of Threads.@spawn. If you're
# plotting live data being read from a different thread, consider
# putting all the updates into a channel that your render function
# empties within the render loop.
data_task = errormonitor(@async live_data(data))
end

ig.MakieFigure("plot", f; auto_resize_x, auto_resize_y, tooltip, stats)
Expand Down
24 changes: 15 additions & 9 deletions ext/MakieIntegration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Statistics: mean, std
import CImGui as ig
import ModernGL as gl
import GLFW

import GLMakie
import GLMakie.Makie as Makie

Expand All @@ -30,27 +31,27 @@ mutable struct ImMakieFigure
end
end

const makie_context = Dict{ig.ImGuiID, ImMakieFigure}()
const makie_context = IdDict{GLMakie.Figure, ImMakieFigure}()

function destroy_context()
for imfigure in values(makie_context)
empty!(imfigure.figure)
for f in keys(makie_context)
empty!(f)
end

empty!(makie_context)
end

Base.isopen(window::ImMakieWindow) = isopen(window.glfw_window)
GLMakie.framebuffer_size(window::ImMakieWindow) = GLMakie.framebuffer_size(window.glfw_window)
GLMakie.scale_factor(window::ImMakieWindow) = GLMakie.scale_factor(window.glfw_window)
GLMakie.was_destroyed(window::ImMakieWindow) = GLMakie.was_destroyed(window.glfw_window)
GLMakie.scale_factor(window::ImMakieWindow)::Float32 = GLMakie.scale_factor(window.glfw_window)
GLMakie.was_destroyed(window::ImMakieWindow)::Bool = GLMakie.was_destroyed(window.glfw_window)
GLMakie.reopen!(x::GLMakie.Screen{ImMakieWindow}) = x
GLMakie.destroy!(::ImMakieWindow) = nothing
GLMakie.set_screen_visibility!(::GLMakie.Screen{ImMakieWindow}, ::Bool) = nothing

# ShaderAbstractions support
GLMakie.ShaderAbstractions.native_switch_context!(x::ImMakieWindow) = GLFW.MakeContextCurrent(x.glfw_window)
GLMakie.ShaderAbstractions.native_context_alive(x::ImMakieWindow) = GLFW.is_initialized() && x.glfw_window != C_NULL
GLMakie.ShaderAbstractions.native_context_alive(x::ImMakieWindow)::Bool = GLFW.is_initialized() && x.glfw_window != C_NULL

# This is called by GLMakie.display() to set up connections to GLFW for
# mouse/keyboard events etc. We disable it explicitly because we deliver the
Expand Down Expand Up @@ -129,24 +130,29 @@ function draw_popup(axis)
end
end

function ig.delete_figure!(f::GLMakie.Figure)
delete!(makie_context, f)
return nothing
end

function ig.MakieFigure(title_id::String, f::GLMakie.Figure;
auto_resize_x=true, auto_resize_y=false,
tooltip=true, stats=false)
ig.PushID(title_id)
id = ig.GetID(title_id)

if !haskey(makie_context, id)
if !haskey(makie_context, f)
window = ig.current_window()
makie_window = ImMakieWindow(window)
screen = GLMakie.Screen(; window=makie_window, start_renderloop=false)

makie_context[id] = ImMakieFigure(f, screen)
makie_context[f] = ImMakieFigure(f, screen)
scene = Makie.get_scene(f)
scene.events.window_open[] = true
display(screen, f)
end

imfigure = makie_context[id]
imfigure = makie_context[f]
scene = Makie.get_scene(f)

region_avail = ig.GetContentRegionAvail()
Expand Down
21 changes: 17 additions & 4 deletions src/CImGui.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ function pintask!(task::Task, tid::Integer)
end
end

# This is implemented by the MakieIntegration extension but we document it here
# so that we don't have to install GLMakie to build the docs.
# The Makie functions are implemented by the MakieIntegration extension but we
# document it here so that we don't have to install GLMakie to build the docs.
"""
MakieFigure(id::String, f::GLMakie.Figure;
auto_resize_x=true, auto_resize_y=false,
Expand Down Expand Up @@ -137,6 +137,18 @@ Known issues:
"""
function MakieFigure end

"""
delete_figure!(f::GLMakie.Figure)

Delete a figure from the internal CImGui state.

When [`MakieFigure()`](@ref) is called it will create some internal state for
the figure. If your application ever 'deletes' a figure (i.e. hiding it so it
shouldn't be displayed again) then you should call this function to clear the
internal state and allow the figure to be GC'd.
"""
function delete_figure! end

## Backends

const _exit_handlers = Function[]
Expand Down Expand Up @@ -272,9 +284,10 @@ function __init__()
if isempty(methods(exc.f))
print(io, "\nrender() cannot be called yet. You must load the packages for supported backends, e.g. `import ModernGL, GLFW` for the GLFW/OpenGL3 backend.")
end
elseif exc.f === MakieFigure
elseif exc.f === MakieFigure || exc.f === delete_figure!
name = nameof(exc.f)
if isempty(methods(exc.f))
print(io, "\nMakieFigure() cannot be called yet, you must load GLMakie with e.g. `import GLMakie`.")
print(io, "\n$(name)() cannot be called yet, you must load GLMakie with e.g. `import GLMakie`.")
end
end
end
Expand Down