Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
929b429
add a simple GUI
SimonDanisch Dec 19, 2023
ba583fa
add GUI for a high level plotting GUI + auto legend/colorbar
SimonDanisch Dec 19, 2023
7b4b63e
merge master
SimonDanisch Dec 2, 2025
a1cdd21
clean up implementation
SimonDanisch Dec 3, 2025
6cc33c2
runic & changelog
SimonDanisch Dec 3, 2025
ff433ea
make title unique
SimonDanisch Dec 3, 2025
dc67e77
Merge branch 'master' into sd/highlevel-gui
SimonDanisch Dec 23, 2025
2d1f4f0
Merge branch 'master' into sd/highlevel-gui
SimonDanisch Jan 4, 2026
d451663
add table widget
SimonDanisch Jan 28, 2026
1e3f8a4
pick up some improvements from Julius
SimonDanisch Jan 29, 2026
5005f2a
Merge branch 'master' into sd/table-widget
SimonDanisch May 11, 2026
7f45bd8
feat(theming): Add derive_colors and a nested colors theme block
jkrumbiegel May 13, 2026
09e386a
feat(makielayout): Route Block color defaults through the colors theme
jkrumbiegel May 13, 2026
a68a1b5
docs(changelog): Note the Block color scheme
jkrumbiegel May 13, 2026
9207591
fix(menu): Reset button hover indicator when cursor enters dropdown
jkrumbiegel May 13, 2026
f0e1ac6
refactor(makielayout): Drop literal fallbacks for nested @inherit
jkrumbiegel May 13, 2026
b9eb155
docs(theming): Add a page on the Block color scheme
jkrumbiegel May 13, 2026
737bba8
docs(theming): Use resize_to_layout! for block-colors showcase
jkrumbiegel May 13, 2026
dfd1ef9
refactor(theming): Mix derived colors in Oklab
jkrumbiegel May 13, 2026
87ca075
docs(changelog): Link block color scheme entry to its PR
jkrumbiegel May 13, 2026
d9a1610
refactor(theming): Reuse existing utilities in derive_colors
jkrumbiegel May 18, 2026
247c66d
refactor(makielayout): Simplify and type-stabilize _inherit_nested
jkrumbiegel May 18, 2026
73b9dba
docs(changelog): Trim block color scheme entry
jkrumbiegel May 18, 2026
ec7256d
docs(changelog): Fold compat note into the lead bullet
jkrumbiegel May 18, 2026
d6ce06e
small fixes and merge
SimonDanisch May 27, 2026
f07c96a
feat(scene): event isolation, visibility cascade, ancestor scissor cl…
jkrumbiegel May 28, 2026
fdd86d7
feat(layout): recurse update_state_before_display! into nested GridLa…
jkrumbiegel May 28, 2026
997efe3
feat(makielayout): Tabs Block with isolated events and scrollable con…
jkrumbiegel May 28, 2026
f1435f9
test(scene): isolation tests for forward_events! and Tabs
jkrumbiegel May 28, 2026
c52b481
refactor(scene): replace effective_viewport + needs_ancestor_clip wit…
jkrumbiegel May 28, 2026
c0be06f
feat(camera): cam_window_pixel! for block-hosting subscenes
jkrumbiegel May 28, 2026
e07a10a
refactor(makielayout): collapse Tabs to one scene per tab
jkrumbiegel May 28, 2026
64fb4b7
feat(makielayout): extract Subfigure as a standalone block
jkrumbiegel May 28, 2026
1b3019c
fix(makielayout): keep Subfigure.scene.visible bound to `active`
jkrumbiegel May 28, 2026
5db4fb5
feat(makielayout): rounded capsule scrollbar thumbs, narrower bar, tr…
jkrumbiegel May 28, 2026
45c723e
feat(docs): add Scroll event to FakeInteraction
jkrumbiegel May 28, 2026
f9e9fba
docs(tabs): add reference page with interaction video
jkrumbiegel May 28, 2026
cfa7ce8
refactor(camera): make campixel! window-pixel-absolute everywhere
jkrumbiegel May 28, 2026
7f7e718
refactor(makielayout): rename Subfigure `active` attribute to `visible`
jkrumbiegel May 28, 2026
65292c2
fix: clipping, scrollbar drag, wheel hit-test, label shrinkage, getin…
jkrumbiegel May 28, 2026
24d34d5
feat(makielayout): close button per tab
jkrumbiegel May 28, 2026
a8ee498
docs(tabs): video shows axis interactivity + close-tab in the demo
jkrumbiegel May 29, 2026
ceafd2a
feat(makielayout): per-tab closable; fix close reindexing
jkrumbiegel May 29, 2026
9cf7c86
refactor(makielayout): Tabs vector-of-struct API with setters
jkrumbiegel May 29, 2026
7c8cea2
docs(tabs): document the runtime setter API
jkrumbiegel May 29, 2026
a7906cd
refactor(makielayout): route add_tab! tab-property kwargs through set…
jkrumbiegel May 29, 2026
ca40aba
docs(changelog): add Tabs/Subfigure entry
jkrumbiegel May 29, 2026
5090e16
docs(changelog): shorten Tabs entry
jkrumbiegel May 29, 2026
851a53e
docs(changelog): fix duplicated PR link
jkrumbiegel May 29, 2026
22ca6e6
Merge remote-tracking branch 'origin' into sd/table-widget
SimonDanisch Jun 3, 2026
3e1b3e8
ignore events implementation
SimonDanisch Jun 23, 2026
50a531e
Merge PR #5628: theme-driven color scheme for Block widgets
SimonDanisch Jul 16, 2026
cfbc77f
Merge PR #5650: Tabs and Subfigure blocks
SimonDanisch Jul 16, 2026
f231b26
Merge PR #5510: Table widget + pointer-event routing
SimonDanisch Jul 16, 2026
a3e9c0e
Merge PR #3491: high-level figure GUI + auto legend/colorbar
SimonDanisch Jul 16, 2026
5467064
Fix BezierPath bbox for axis-aligned paths (unblocks Table marker)
SimonDanisch Jul 16, 2026
4f6911b
Fix Table sort/select/scroll: update block graph, not the plot
SimonDanisch Jul 16, 2026
dbb9ab0
Restyle HoverMenu to the theme color scheme
SimonDanisch Jul 16, 2026
6e2d3c5
Tabs: use shared events + receives_events isolation (drop forward_eve…
SimonDanisch Jul 16, 2026
e6105e3
Fix widget hit-testing inside offset-viewport scenes (Subfigure/Tabs)
SimonDanisch Jul 16, 2026
e865401
Add Modal block + Scene.captures_mouse pointer capture
SimonDanisch Jul 16, 2026
f88e4fa
Run formatter
SimonDanisch Jul 16, 2026
45af685
fake_interaction: add WaitUntil(pred; timeout) event
SimonDanisch Jul 19, 2026
be162ea
GLMakie: don't crash the first render on an unlaid-out viewport
SimonDanisch Jul 20, 2026
c35ba2b
add paramform
SimonDanisch Jul 21, 2026
0adad2d
clean up and remove forward_events!
SimonDanisch Jul 21, 2026
712564b
Merge branch 'sd/breaking-gui' of https://github.com/MakieOrg/Makie.j…
SimonDanisch Jul 21, 2026
1005179
make pixelcam absolute mode optional instead of breaking
SimonDanisch Jul 21, 2026
556e65a
ParamForm accessory column + Modal halign/valign
SimonDanisch Jul 21, 2026
8b423a6
menu: survive live options growth; Subfigure: propagate hide! to chil…
SimonDanisch Jul 23, 2026
be6d7f2
Subfigure: unhide! re-syncs the content scene with its bound state
SimonDanisch Jul 24, 2026
e83784a
Subfigure: scrolled-out content stops receiving clicks
SimonDanisch Jul 27, 2026
5056bb7
menu: a search that matches nothing no longer kills the render loop
SimonDanisch Jul 28, 2026
6e51eab
card
SimonDanisch Aug 5, 2026
99520f1
Merge branch 'sd/breaking-gui' of https://github.com/MakieOrg/Makie.j…
SimonDanisch Aug 5, 2026
fe9da6d
Spinner: braille-frame busy indicator block
SimonDanisch Aug 7, 2026
5df9198
Spinner: drive animation from tick events
SimonDanisch Aug 7, 2026
789d268
Modal: replace_content! and max_size
SimonDanisch Aug 11, 2026
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## Unreleased

- `Subfigure` now anchors its content to the top-left instead of centring it. A `GridLayout` defaults to `valign = :center`, so content shorter than the scroll region floated in the middle of it; when content overflows the alignment has no effect, so this only changes the short case.
- Added a `Card` block: a titled, foldable container for building lists of settings panels. `open` folds the body away and leaves the header; `visible = false` COLLAPSES the card — unlike `hide!`, which stops a block drawing but leaves its row at full height, so a filtered list showed holes where its hidden entries used to be. A card's bottom spacing is part of the card rather than a gap in the parent layout, so hiding one leaves no double gap; build the stack with `default_rowgap = 0`. `filter_cards!(predicate, stack, cards)` applies a filter in a single relayout (measured over 100 cards, toggling 50: 147 ms one at a time, 14 ms batched, 433 ms to rebuild them instead). Header widgets go in `card_accessory(card)`, and `headerclicks` counts presses on the title bar for list selection.
- Fixed a GLMakie `InexactError` that killed the first render of a fresh window when a scene's viewport had not been laid out yet: the empty-`Rect2i()` sentinel (typemax/typemin) makes `effective_clip`'s Int64 `intersect` overflow to a garbage-but-finite rect, and rounding that to `Int` for `glViewport`/`glScissor` threw. The per-scene scissor code now clamps each pixel extent to a sane `GLint` range (`gl_extent`).
- Added a unified `colors` theme block for Block widgets, plus a `Makie.derive_colors(; accent, gray, background)` helper that produces a full scheme from a few inputs. `set_theme!(colors = Makie.derive_colors(accent = :crimson))` recolors every interactive Block consistently — see the Block colors docs. Default neutral surfaces shift very slightly from the historical literals. [#5628](https://github.com/MakieOrg/Makie.jl/pull/5628)
- The `@inherit` macro inside `@Block` now accepts a tuple of symbols (`@inherit((:colors, :accent), default)`) to walk nested theme dicts.
- Added `Tabs` and `Subfigure` blocks, a tabbed container and the scrollable, event-isolated sub-region it is built on. [#5650](https://github.com/MakieOrg/Makie.jl/pull/5650)
- Added a `Table` block for displaying tabular data, with row/cell selection, column sorting and scrolling. [#5510](https://github.com/MakieOrg/Makie.jl/pull/5510)
- Added pointer-event routing based on visual scene stacking so overlay scenes (menus, dropdowns, popups) no longer leak mouse events to widgets underneath. `receives_events(scene)` returns `false` when a scene in a different subtree visually covers the pointer, and `is_mouseinside(scene)` honors it automatically. [#5510](https://github.com/MakieOrg/Makie.jl/pull/5510)
- Added a high-level figure GUI: a `HoverMenu` toolbar (save / copy / reset) plus opt-in automatic `Legend`/`Colorbar` insertion, controlled via `Figure(; gui, legend, colorbar)` or the theme. Also added convenience constructors `Legend(ax; ...)`, `Colorbar(ax, plot; ...)` and `axiscolorbar`. [#3491](https://github.com/MakieOrg/Makie.jl/pull/3491)
- Fixed a searchable `Menu` dying on a query that matches no option: the empty option list left its per-option color vectors empty, and the next query that did match resolved the option text plot against them and threw a `BoundsError` — inside the compute graph, which takes the render loop down with it. Per-option positions, rects and colors are now resized before the text plot sees the new strings, and `per_glyph_block` no longer indexes into an empty per-string attribute vector.
- Added a `Modal` block: a theme-styled dialog with a translucent backdrop that blocks pointer input to everything underneath while open (`open!`/`close!`, × button, optional backdrop-click dismissal). The content area is a `Subfigure`, so fixed-size modals scroll. Scenes can opt into pointer capture explicitly with `scene.captures_mouse = true`.

## Breaking

- **breaking** Moved `FFMPEG_jll` from a hard dependency to a package extension to avoid pulling in GPL-licensed libraries (e.g. libx264). `record`, `VideoStream`, `convert_video`, and `extract_frames` now require `FFMPEG_jll` to be available in the active environment; Makie will load it automatically on first use. A custom ffmpeg binary can be configured via `Makie.ffmpeg_path!(path)` (or persistently via Preferences.jl). [#5588](https://github.com/MakieOrg/Makie.jl/pull/5588)
Expand Down
2 changes: 1 addition & 1 deletion CairoMakie/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ FileIO = "1.1"
FreeType = "3, 4.0"
GeometryBasics = "0.5"
LinearAlgebra = "1.0, 1.6"
Makie = "=0.25.0"
Makie = "=0.24.13"
PrecompileTools = "1.0"
julia = "1"

Expand Down
31 changes: 18 additions & 13 deletions CairoMakie/src/plot-primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function cairo_draw(screen::Screen, scene::Scene)
# only prepare for scene when it changes
# this should reduce the number of unnecessary clipping masks etc.
pparent = Makie.parent_scene(p)::Scene
pparent.visible[]::Bool || continue
Makie.scene_visible(pparent) || continue
if pparent != last_scene
Cairo.restore(screen.context)
Cairo.save(screen.context)
Expand Down Expand Up @@ -92,22 +92,27 @@ function prepare_for_scene(screen::Screen, scene::Scene)

# get the root area to correct for its size when translating
root_area_height = widths(Makie.root(scene))[2]
scene_area = viewport(scene)[]
scene_height = widths(scene_area)[2]
scene_x_origin, scene_y_origin = scene_area.origin

# we need to translate x by the origin, so distance from the left
# but y by the distance from the top, which is not the origin, but can
# be calculated using the parent's height, the scene's height and the y origin
# this is because y goes downwards in Cairo and upwards in Makie
# Clip to the intersection of all ancestor viewports — `effective_clip`
# — so the scene only renders within the bounds its parents share.
# The scene's own viewport is excluded from the intersection, so e.g.
# axis markers near a spine extend past the plot scene into the axis's
# decoration area (which is clipped at the figure / container edge).
clip_area = Makie.effective_clip(scene)
clip_x, clip_y_makie = origin(clip_area)
clip_w, clip_h = widths(clip_area)
clip_top = root_area_height - clip_y_makie - clip_h
Cairo.rectangle(screen.context, clip_x, clip_top, clip_w, clip_h)
Cairo.clip(screen.context)

# Translate so scene-local (0, 0) corresponds to the scene's window origin.
# Cairo's y goes down, Makie's goes up, hence the parent-height correction.
scene_area = viewport(scene)[]
scene_x_origin, scene_y_origin = scene_area.origin
scene_height = widths(scene_area)[2]
top_offset = root_area_height - scene_height - scene_y_origin
Cairo.translate(screen.context, scene_x_origin, top_offset)

# clip the scene to its viewport
Cairo.rectangle(screen.context, 0, 0, widths(scene_area)...)
Cairo.clip(screen.context)

return
end

Expand All @@ -119,7 +124,7 @@ end
function draw_background(screen::Screen, scene::Scene, root_h)
cr = screen.context
Cairo.save(cr)
if scene.clear[]
if scene.clear[] && Makie.scene_visible(scene)
bg = scene.backgroundcolor[]
Cairo.set_source_rgba(cr, red(bg), green(bg), blue(bg), alpha(bg))
r = viewport(scene)[]
Expand Down
2 changes: 1 addition & 1 deletion GLMakie/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ FreeTypeAbstraction = "0.10"
GLFW = "3.4.3"
GeometryBasics = "0.5"
LinearAlgebra = "1.0, 1.6"
Makie = "=0.25.0"
Makie = "=0.24.13"
Markdown = "1.0, 1.6"
MeshIO = "0.5"
ModernGL = "1"
Expand Down
21 changes: 19 additions & 2 deletions GLMakie/src/postprocessing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ end

on_resize(stage::RenderPlots, w, h) = resize!(stage.framebuffer, w, h)

# glViewport / glScissor take GLint pixel extents. A scene whose viewport hasn't
# been laid out yet carries the empty-`Rect2i()` sentinel (typemax/typemin), which
# makes `effective_clip`'s Int64 `intersect` overflow to a garbage-but-finite rect;
# `round(Int, ppu .* that)` then throws `InexactError` and kills the whole frame
# (seen as "Error while rendering!" on the very first render of a fresh window).
# Clamp each component to a sane pixel range so a transient pre-layout viewport
# degrades to a valid rect instead of crashing the render loop.
@inline gl_extent(x::Real) = isfinite(x) ? round(GLint, clamp(x, -1.0f8, 1.0f8)) : GLint(0)

function run_stage(screen, glscene, stage::RenderPlots)
# Somehow errors in here get ignored silently!?
try
Expand All @@ -301,23 +310,31 @@ function run_stage(screen, glscene, stage::RenderPlots)

set_draw_buffers(stage.framebuffer)

glEnable(GL_SCISSOR_TEST)
for (zindex, screenid, elem) in screen.renderlist
elem.visible && haskey(elem.variants, stage.target) || continue

found, scene = id2scene(screen, screenid)
(found && scene.visible[]) || continue
(found && Makie.scene_visible(scene)) || continue

ppu = screen.px_per_unit[]
a = viewport(scene)[]
# Scissor to the intersection of all ancestor viewports (excluding
# the scene's own) so each scene draws only within the bounds its
# parents share; lets markers near a scene edge extend past it
# while still being cut off at the enclosing container / window.
sa = Makie.effective_clip(scene)

require_context(screen.glscreen)
glViewport(round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...)
glViewport(gl_extent.(ppu .* minimum(a))..., gl_extent.(ppu .* widths(a))...)
glScissor(gl_extent.(ppu .* minimum(sa))..., gl_extent.(ppu .* widths(sa))...)
elem[:px_per_unit] = ppu

stage.prerender(elem[:overdraw]::UInt8)

render(elem, elem.variants[stage.target])
end
glDisable(GL_SCISSOR_TEST)
catch e
@error "Error while rendering!" exception = e
rethrow(e)
Expand Down
8 changes: 4 additions & 4 deletions GLMakie/src/rendering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ function setup!(screen::Screen, fb)
glEnable(GL_SCISSOR_TEST)
ppu = screen.px_per_unit[]
for (id, scene) in screen.screens
if scene.visible[] && scene.clear[]
if Makie.scene_visible(scene) && scene.clear[]
a = viewport(scene)[]
rt = (round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...)
glViewport(rt...)
glScissor(rt...)
sa = Makie.effective_clip(scene)
glViewport(round.(Int, ppu .* minimum(a))..., round.(Int, ppu .* widths(a))...)
glScissor(round.(Int, ppu .* minimum(sa))..., round.(Int, ppu .* widths(sa))...)
c = scene.backgroundcolor[]
glClearColor(red(c), green(c), blue(c), alpha(c))
glClear(GL_COLOR_BUFFER_BIT)
Expand Down
8 changes: 6 additions & 2 deletions Makie/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Makie"
uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
authors = ["Simon Danisch", "Julius Krumbiegel"]
version = "0.25.0"
version = "0.24.13"

[deps]
Animations = "27a7e980-b3e6-11e9-2bcd-0b925532e340"
Expand All @@ -27,6 +27,7 @@ FreeTypeAbstraction = "663a7486-cb36-511b-a19d-713bb74d65c9"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
GridLayoutBase = "3955a311-db13-416c-9275-1d80ed98e5e9"
ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e"
ImageClipboard = "6db54171-f50f-4661-a74f-bc514ef16cee"
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
Expand All @@ -39,6 +40,7 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
MathTeXEngine = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53"
NativeFileDialog_jll = "94d9ae2c-efc7-56f8-9a02-54c47b797961"
Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
PNGFiles = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883"
Expand Down Expand Up @@ -95,9 +97,10 @@ FixedPointNumbers = "0.6, 0.7, 0.8"
Format = "1.3"
FreeType = "3.0, 4.0"
FreeTypeAbstraction = "0.10.3"
GeometryBasics = "0.5.9"
GeometryBasics = "0.5.11"
GridLayoutBase = "0.11"
ImageBase = "0.1.7"
ImageClipboard = "1.0.0"
ImageIO = "0.5, 0.6"
InteractiveUtils = "1.0, 1.6"
Interpolations = "0.14, 0.15.1, 0.16"
Expand All @@ -110,6 +113,7 @@ LinearAlgebra = "1.0, 1.6"
MacroTools = "0.5"
Markdown = "1.0, 1.6"
MathTeXEngine = "0.5, 0.6"
NativeFileDialog_jll = "1.1.6"
Observables = "0.5.5"
OffsetArrays = "1"
PNGFiles = "0.4.4"
Expand Down
35 changes: 35 additions & 0 deletions Makie/src/GUI/file-dialogue.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using NativeFileDialog_jll

function choose_file_dialogue(filter = C_NULL)
path = Ref(Ptr{UInt8}())
r = @ccall libnfd.NFD_OpenDialog(
filter::Ptr{Cchar}, C_NULL::Ptr{Cchar},
path::Ref{Ptr{UInt8}}
)::Cint
if r == 2
# User clicked "Cancel"
out = nothing
elseif r == 1
out = unsafe_string(path[])
else
error()
end
return out
end

function save_file_dialogue(filter = C_NULL)
path = Ref(Ptr{UInt8}())
r = @ccall libnfd.NFD_SaveDialog(
filter::Ptr{Cchar}, C_NULL::Ptr{Cchar},
path::Ref{Ptr{UInt8}}
)::Cint
if r == 2
# User clicked "Cancel"
out = nothing
elseif r == 1
out = unsafe_string(path[])
else
error()
end
return out
end
Loading