diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7bb35e2c9..443357129ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +- Add a global Axis/Axis3D registry for default interactions [#4531](https://github.com/MakieOrg/Makie.jl/pull/4531). + ## [0.21.15] - 2024-10-25 - Allowed creation of `Legend` with entries that have no legend elements [#4526](https://github.com/MakieOrg/Makie.jl/pull/4526). diff --git a/src/interaction/events.jl b/src/interaction/events.jl index 20e10ce29a6..c1f9f383e4d 100644 --- a/src/interaction/events.jl +++ b/src/interaction/events.jl @@ -288,6 +288,7 @@ See also: [`And`](@ref), [`Or`](@ref), [`Not`](@ref), [`Exclusively`](@ref), ispressed(events::Events, mb::Mouse.Button, waspressed = nothing) = mb in events.mousebuttonstate || mb == waspressed ispressed(events::Events, key::Keyboard.Button, waspressed = nothing) = key in events.keyboardstate || key == waspressed ispressed(parent, result::Bool, waspressed = nothing) = result +ispressed(parent, result::Nothing, waspressed = nothing) = true ispressed(parent, mb::Mouse.Button, waspressed = nothing) = ispressed(events(parent), mb, waspressed) ispressed(parent, key::Keyboard.Button, waspressed = nothing) = ispressed(events(parent), key, waspressed) diff --git a/src/makielayout/blocks/axis.jl b/src/makielayout/blocks/axis.jl index 0213500642e..8b532adb832 100644 --- a/src/makielayout/blocks/axis.jl +++ b/src/makielayout/blocks/axis.jl @@ -1,3 +1,10 @@ +const DEFAULT_AXIS_INTERACTIONS = Dict( + :rectanglezoom => () -> RectangleZoom(), + :limitreset => () -> LimitReset(), + :scrollzoom => () -> ScrollZoom(0.1, 0.2), + :dragpan => () -> DragPan(0.2), +) + function update_gridlines!(grid_obs::Observable{Vector{Point2f}}, offset::Point2f, tickpositions::Vector{Point2f}) result = grid_obs[] empty!(result) # reuse array for less allocations @@ -49,13 +56,9 @@ function register_events!(ax, scene) onany(process_axis_event, scene, ax, scrollevents) onany(process_axis_event, scene, ax, keysevents) - register_interaction!(ax, :rectanglezoom, RectangleZoom(ax)) - - register_interaction!(ax, :limitreset, LimitReset()) - - register_interaction!(ax, :scrollzoom, ScrollZoom(0.1, 0.2)) - - register_interaction!(ax, :dragpan, DragPan(0.2)) + for (name, fn) in DEFAULT_AXIS_INTERACTIONS + register_interaction!(ax, name, fn()) + end return end diff --git a/src/makielayout/blocks/axis3d.jl b/src/makielayout/blocks/axis3d.jl index a6b97462925..5fa57e95b22 100644 --- a/src/makielayout/blocks/axis3d.jl +++ b/src/makielayout/blocks/axis3d.jl @@ -1,3 +1,7 @@ +const DEFAULT_AXIS_3D_INTERACTIONS = Dict( + :dragrotate => () -> DragRotate(), +) + struct OrthographicCamera <: AbstractCamera end function initialize_block!(ax::Axis3) @@ -158,10 +162,9 @@ function initialize_block!(ax::Axis3) on(process_event, scene, ax.scrollevents) on(process_event, scene, ax.keysevents) - register_interaction!(ax, - :dragrotate, - DragRotate()) - + for (name, fn) in DEFAULT_AXIS_3D_INTERACTIONS + register_interaction!(ax, name, fn()) + end # in case the user set limits already notify(ax.limits) diff --git a/src/makielayout/interactions.jl b/src/makielayout/interactions.jl index 5d75285b5d3..016653b8775 100644 --- a/src/makielayout/interactions.jl +++ b/src/makielayout/interactions.jl @@ -54,11 +54,11 @@ function deregister_interaction!(parent, name::Symbol) end function registration_setup!(parent, interaction) - # do nothing in the default case + return parent # do nothing in the default case end function deregistration_cleanup!(parent, interaction) - # do nothing in the default case + return parent # do nothing in the default case end """ @@ -177,10 +177,9 @@ function process_interaction(r::RectangleZoom, event::MouseEvent, ax::Axis) return Consume(true) elseif event.type === MouseEventTypes.leftdragstop - try - r.callback(r.rectnode[]) - catch e - @warn "error in rectangle zoom" exception=(e, Base.catch_backtrace()) + newlims = r.rectnode[] + if !(0 in widths(newlims)) + ax.targetlimits[] = newlims end r.active[] = false return Consume(true) @@ -200,6 +199,12 @@ function process_interaction(r::RectangleZoom, event::KeysEvent, ax::Axis) r.restrict_x = Keyboard.y in event.keys r.active[] || return Consume(false) + # Deactivate when modifier is released before the mouse. + if r.modifier !== true && r.modifier ∉ event.keys + r.active[] = false + return Consume(true) + end + r.rectnode[] = _chosen_limits(r, ax) return Consume(true) end @@ -211,11 +216,11 @@ function positivize(r::Rect2) return Rect2(newori, newwidths) end -function process_interaction(::LimitReset, event::MouseEvent, ax::Axis) +function process_interaction(l::LimitReset, event::MouseEvent, ax::Axis) - if event.type === MouseEventTypes.leftclick - if ispressed(ax.scene, Keyboard.left_control) - if ispressed(ax.scene, Keyboard.left_shift) + if event.type === l.mouseevent + if ispressed(ax.scene, l.modifier1) + if ispressed(ax.scene, l.modifier2) autolimits!(ax) else reset_limits!(ax) diff --git a/src/makielayout/types.jl b/src/makielayout/types.jl index 4da455bf423..3f48f56667f 100644 --- a/src/makielayout/types.jl +++ b/src/makielayout/types.jl @@ -149,10 +149,22 @@ mutable struct LineAxis minortickvalues::Observable{Vector{Float32}} end -struct LimitReset end + +struct LimitReset + mouseevent::MouseEventTypes.MouseEventType # e.g. MouseEventTypes.leftclick, or some other mouse event to start limit reset. + modifier1::Optional{Keyboard.Button} # e.g. Keyboard.left_control, or some other keyboard button to reset limits. + modifier2::Optional{Keyboard.Button} # e.g. Keyboard.left_shift, or some other keyboard button to auto limits. + + function LimitReset( + mouseevent = MouseEventTypes.leftclick, + modifier1 = Keyboard.left_control, + modifier2 = Keyboard.left_shift, + ) + new(mouseevent, modifier1, modifier2) + end +end mutable struct RectangleZoom - callback::Function active::Observable{Bool} restrict_x::Bool restrict_y::Bool @@ -162,8 +174,8 @@ mutable struct RectangleZoom modifier::Any # e.g. Keyboard.left_alt, or some other button that needs to be pressed to start rectangle... Defaults to `true`, which means no modifier needed end -function RectangleZoom(callback::Function; restrict_x=false, restrict_y=false, modifier=true) - return RectangleZoom(callback, Observable(false), restrict_x, restrict_y, +function RectangleZoom(restrict_x=false, restrict_y=false, modifier=true) + return RectangleZoom(Observable(false), restrict_x, restrict_y, nothing, nothing, Observable(Rect2d(0, 0, 1, 1)), modifier) end @@ -190,9 +202,8 @@ function DragPan(reset_delay) return DragPan(RefValue{Union{Nothing, Timer}}(nothing), RefValue{Union{Automatic, Float64}}(0.0), RefValue{Union{Automatic, Float64}}(0.0), reset_delay) end +struct DragRotate end -struct DragRotate -end struct ScrollEvent x::Float32 @@ -679,8 +690,8 @@ Axis(fig_or_scene; palette = nothing, kwargs...) end end -function RectangleZoom(f::Function, ax::Axis; kw...) - r = RectangleZoom(f; kw...) + +function registration_setup!(ax::Axis, r::RectangleZoom) rect_scene = Scene(ax.scene) selection_vertices = lift(_selection_vertices, rect_scene, Observable(ax.scene), ax.finallimits, r.rectnode) @@ -694,18 +705,16 @@ function RectangleZoom(f::Function, ax::Axis; kw...) inspectable = false, transparency=true, overdraw=true, visible=r.active) # translate forward so selection mesh and frame are never behind data translate!(mesh, 0, 0, 1000) - return r + + return ax end -function RectangleZoom(ax::Axis; kw...) - return RectangleZoom(ax; kw...) do newlims - if !(0 in widths(newlims)) - ax.targetlimits[] = newlims - end - return - end +function deregistration_cleanup!(ax::Axis, r::RectangleZoom) + # TODO: Remove mesh? + return ax end + """ Create a colorbar that shows a continuous or categorical colormap with ticks chosen according to the colorrange.