From 0b9aeea7bcdb4dda88a8334c439b60ab4a1c86d2 Mon Sep 17 00:00:00 2001 From: uitt_hu Date: Sun, 15 Nov 2026 15:49:01 +0100 Subject: [PATCH] The card of a detector switches "stop beams" in place (#71) A toggle on the page "Pose" of the card of every detector, also one of the user's script: it calls BeamletOptics.stop! on the same object (the solve is cancelled first, then solved again; undo switches back) and writes the state into the code of a catalog, project or code detector in every linked window, also into a pending input of the page "Edit", which has no switch of its own any more. export_changes writes BeamletOptics.stop!(name, v) for a detector without code. A step of the precompile session clicks the switch. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 4 +- docs/src/components.md | 2 +- docs/src/widgets.md | 3 +- ext/BeamletOpticsGUIGLMakieExt/session.jl | 21 +++- skills/beamletopticsgui/API.md | 4 +- skills/beamletopticsgui/VISUALIZATION.md | 3 +- src/BeamletOpticsGUI.jl | 1 + src/LiveCardRows.jl | 3 +- src/LiveDetectorStop.jl | 93 +++++++++++++++++ src/LiveEdit.jl | 3 + src/LiveExport.jl | 29 +++++ src/LiveView.jl | 3 + test/TestLiveDetectorStop.jl | 122 ++++++++++++++++++++++ test/TestLiveEdit.jl | 33 +++--- test/TestPrecompileSession.jl | 9 +- 15 files changed, 305 insertions(+), 28 deletions(-) create mode 100644 src/LiveDetectorStop.jl create mode 100644 test/TestLiveDetectorStop.jl diff --git a/AGENTS.md b/AGENTS.md index b4d3ce6..54c6f5f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,7 +86,9 @@ describe it as available in docs or the skill. - `src/LiveCard.jl`, `src/LiveCards.jl`, `src/LiveCardRows.jl`: cards and the card API. `src/LiveCardPages.jl`: the pages of a card per type ("Pose", "Color", "Results", "Properties"). `src/LiveBeamColor.jl`: the page "Color" of the sources (color, opacity and line width of their - beams). `src/LiveEdit.jl`: the page "Edit" of the components and sources from the catalog, which + beams). `src/LiveDetectorStop.jl`: the switch "stop beams" on the card of a detector, which + switches it in place (`BeamletOptics.stop!`) and writes the state into its code. + `src/LiveEdit.jl`: the page "Edit" of the components and sources from the catalog, which builds them again with other parameters. `src/LiveCopy.jl`: `Ctrl+C` and `Ctrl+V`, which build one of them again as it is and place it with the mouse. Adding, removing and changing are actions of the undo history of the controls (`_push_action!` in `src/LiveInteraction.jl`, `_record_added!` in diff --git a/docs/src/components.md b/docs/src/components.md index b273063..b44a872 100644 --- a/docs/src/components.md +++ b/docs/src/components.md @@ -104,7 +104,7 @@ takes numbers and glasses: | Beamsplitters | Thin beamsplitter, Round thin beamsplitter, Plate beamsplitter, Round plate beamsplitter, Cube beamsplitter, Compensator plate | | Prisms | Right-angle prism | | Polarizers | Polarization filter, Round filter, Linear polarizer | -| Detectors | Detector, with the switch "stop beams": off, the beams pass it unchanged (`Detector(edge_length, false)`) and reach the components behind it | +| Detectors | Detector, with the switch "stop beams": off, the beams pass it unchanged (`Detector(edge_length, false)`) and reach the components behind it; the card of any detector switches it later in place (page "Pose") | The icons at the top of the catalog select the group, the tiles below the entry. The boxes of the form take its numbers; the other arguments of the constructor keep their defaults, e.g. the diff --git a/docs/src/widgets.md b/docs/src/widgets.md index bb23703..8d429f4 100644 --- a/docs/src/widgets.md +++ b/docs/src/widgets.md @@ -116,7 +116,8 @@ BeamletOpticsGUI.card_actions(a::MyAttenuator) = ( ``` The types of BeamletOptics have such methods as well, e.g. the ray count slider of a source or the -signal of a `Detector`. +signal of a `Detector` and its switch "stop beams", which switches the detector itself +(`BeamletOptics.stop!`): it stays the same object, and undo switches it back. ## Recipe: a card for your system type diff --git a/ext/BeamletOpticsGUIGLMakieExt/session.jl b/ext/BeamletOpticsGUIGLMakieExt/session.jl index 20d61bd..30cef41 100644 --- a/ext/BeamletOpticsGUIGLMakieExt/session.jl +++ b/ext/BeamletOpticsGUIGLMakieExt/session.jl @@ -724,6 +724,22 @@ function _session(step, layout::Symbol, fixture::_Fixture = _fixture()) deselect!() end + # The switch "stop beams" on the page "Pose" of the card of the detector: off and on again, such + # that the later steps still see its hits + step("card: stop beams of the detector (off, on)") do + _click!(gui, _px(gui, _pos(fx.detector))) + frame!() + _selected(gui, fx.detector) + GUI._set_page!(gui, card_of(fx.detector, :hide), :pose) + frame!() + for stop in (false, true) + _click!(gui, _rect_center(GUI._card_widget(card_of(fx.detector, :stop), :stop))) + settle!() + _check(BMO.stop(fx.detector) == stop, "the switch did not set stop = $stop") + end + deselect!() + end + # The pencil in the head of the card of a component: a typed name and Enter step("card: rename a component in the title") do _click!(gui, _px(gui, _pos(fx.mirror))) @@ -1264,9 +1280,10 @@ function _catalog_session(step, entries = component_catalog()) settle!() end end - # a switch on the page "Edit", e.g. the detector that lets the beams pass, then "Apply" + # a switch on the page "Edit", then "Apply"; not of a detector, which its card switches in + # place (see the step "card: stop beams of the detector") s = findfirst(p -> p isa GUI.CatalogToggle, entry.params) - if !isnothing(s) + if !isnothing(s) && !isempty(GUI._edit_param_rows(gui, obj, entry.params[s], s)) step("catalog, $(entry.name): edit switch + apply") do gui.controls.selected[] === obj || select!(gui, obj) frame!() diff --git a/skills/beamletopticsgui/API.md b/skills/beamletopticsgui/API.md index e03a1c3..371e4a3 100644 --- a/skills/beamletopticsgui/API.md +++ b/skills/beamletopticsgui/API.md @@ -147,7 +147,9 @@ lens with an asphere is built and exported as the `Lens`, `DoubletLens` or `Trip surfaces, e.g. `Lens(SphericalSurface(r, d), EvenAsphericalSurface(r, d, k, [0.0, A4, ...]), thickness, n)`; a cemented surface belongs to both elements that it joins. The detector has a switch "stop beams" (on by default); off, the beams pass it unchanged (`Detector(edge_length, -false)`) and reach the components behind it. "Place" +false)`) and reach the components behind it. The card of any detector switches it later in place +(page "Pose"; the page "Edit" has no switch), and `export_changes` writes +`BeamletOptics.stop!(name, false)` for a detector without code of its own. "Place" attaches the chosen component to the mouse, a click drops it, `Esc` cancels, `Delete` removes the selected component or source. It moves in the plane of the table (perpendicular to `rotation_axis`, at the height of the source of its system) and snaps onto beams while `snap` is on; the first component or source of diff --git a/skills/beamletopticsgui/VISUALIZATION.md b/skills/beamletopticsgui/VISUALIZATION.md index 3926780..5c6b33c 100644 --- a/skills/beamletopticsgui/VISUALIZATION.md +++ b/skills/beamletopticsgui/VISUALIZATION.md @@ -38,7 +38,8 @@ wavelength in that mode; a color or length change while a long solve runs starts opens on "Results" for a detector, else on "Pose"; a pinned card keeps its page. Rows of the type: rays hitting it and their angle of incidence (last solve), `n` of lenses, R/T of beamsplitters, the polarizer axis, detector `signal` (power or number of rays of its view while a -view is shown, else number of hits), the ray count slider of sources +view is shown, else number of hits) and its switch "stop beams" (in place: the same detector, +`BeamletOptics.stop!`, with undo), the ray count slider of sources (`num_rays!`), and on every beam, beam group, source and beamlet the toggle "on" (off: not solved, not drawn, rays removed from detectors; only the source marker stays), for Gaussian beamlets the toggle "beams" (generating beams as `render!(...; show_beams = true)`), for polarized diff --git a/src/BeamletOpticsGUI.jl b/src/BeamletOpticsGUI.jl index 2e3f772..602a3fd 100644 --- a/src/BeamletOpticsGUI.jl +++ b/src/BeamletOpticsGUI.jl @@ -68,6 +68,7 @@ include("LiveDetectors.jl") include("LiveResultsView.jl") include("LiveTrace.jl") include("LiveBeamSwitch.jl") +include("LiveDetectorStop.jl") include("LivePolarization.jl") include("LiveBeamOverlays.jl") include("LiveClip.jl") diff --git a/src/LiveCardRows.jl b/src/LiveCardRows.jl index 9d6dff6..aa6b6fc 100644 --- a/src/LiveCardRows.jl +++ b/src/LiveCardRows.jl @@ -134,7 +134,8 @@ card_rows(@nospecialize(p::Union{BMO.LinearPolarizer, BMO.PolarizationFilter})) (pose_card_rows(p)..., _beam_row(), _text_row("axis", :axis, _axis_text)) # Detectors: the hits of the last solve, the power, or the number of rays, of the view of the # detector; the view itself is on the page "Results" of the card, see `_has_view` -card_rows(@nospecialize(pd::BMO.Detector)) = (pose_card_rows(pd)..., _beam_row(), _text_row("signal", :signal, _signal_text)) +card_rows(@nospecialize(pd::BMO.Detector)) = (pose_card_rows(pd)..., _beam_row(), _stop_row(), + _text_row("signal", :signal, _signal_text)) # Beams and beam groups: switched on and off, their polarization, see `beam_card_rows` card_rows(@nospecialize(b::Union{BMO.AbstractBeam, BMO.AbstractBeamGroup})) = (pose_card_rows(b)..., beam_card_rows(b)...) # Sources whose rays can be regenerated: wavelength, size and a slider for the number of rays diff --git a/src/LiveDetectorStop.jl b/src/LiveDetectorStop.jl new file mode 100644 index 0000000..36209c6 --- /dev/null +++ b/src/LiveDetectorStop.jl @@ -0,0 +1,93 @@ +#= +The switch "stop beams" on the card of a detector, which changes the detector itself, see `_set_stop!` +=# + +# The row of the switch on the page "Pose" of the card of a `Detector`, see `card_rows` +_stop_row() = CardRow("stop", CardWidget(Toggle; name = :stop, value = (gui, d) -> BMO.stop(d), + on = (gui, d, v) -> _set_stop!(gui, d, v)), "beams") + +""" + _set_stop!(gui, d, stop::Bool) + +Switches whether the detector `d` of the `gui` stops the beams that hit it, in place +(`BeamletOptics.stop!`): it stays the same object, in its systems, with its label and its cards. Like +a move, a running solve is cancelled first, then the systems are solved again, or marked as outdated +without auto tracing, via the `on_change` of the controls. The code of `d` (of the catalog, of a +project or added with `code`) gets the new state in every window that shows `d`, see `_stop_code!`, +such that export, save and copy keep it; `export_changes` writes a `BeamletOptics.stop!` for a +detector without code. An entry of the undo history. Nothing happens if the state does not change. +""" +function _set_stop!(gui::LiveView, @nospecialize(d), stop::Bool) + BMO.stop(d) == stop && return nothing + _change!(() -> BMO.stop!(d, stop), gui.controls, d) + comp = gui.components + haskey(comp.stops0, d) || (comp.stops0[d] = !stop) + # every window of the links that shows `d`, also this one + foreach(v -> _stop_code!(v, d, stop), unique(objectid, Any[gui, gui.links.views...])) + if comp.recording + _push_action!(gui.controls, d, () -> _unrecorded(() -> _set_stop!(gui, d, !stop), gui), + () -> _unrecorded(() -> _set_stop!(gui, d, stop), gui)) + end + _update_cards!(gui) + _update_inspector!(gui) + gui.controls.on_change(d) + return nothing +end + +""" + _stop_code!(gui, d, stop::Bool) + +Writes the state `stop` of the detector `d` into its origin in the `gui` (see `_ComponentState`): for +an entry of the catalog its input of the switch, with which the code is built again (and a pending +input of the page "Edit"); for other code the second argument of its call `Detector(…)`, see +`_stop_call_code`. Code that is no such call keeps its state, and the status line says that the +switch is not saved. Nothing for a detector without code, e.g. one of the user's script. +""" +function _stop_code!(gui::LiveView, @nospecialize(d), stop::Bool) + comp = gui.components + origin = get(comp.origin, d, nothing) + isnothing(origin) && return nothing + i = haskey(origin, :entry) && origin.entry.constructor === BMO.Detector ? + findfirst(_is_toggle, origin.entry.params) : nothing + if !isnothing(i) && haskey(origin, :strings) + strings = copy(origin.strings) + strings[i] = string(stop) + values = Any[_catalog_value(p, s) for (p, s) in zip(origin.entry.params, strings)] + comp.origin[d] = merge(origin, (; code = _catalog_code(origin.entry, values), strings)) + haskey(comp.edits, d) && (comp.edits[d][i] = string(stop)) + return nothing + end + code = _stop_call_code(origin.code, stop) + if isnothing(code) + gui.status.text[] = "the switch of $(_label(gui, d)) is not saved: its code is no call of `Detector`" + return nothing + end + comp.origin[d] = merge(origin, (; code)) + return nothing +end + +_is_toggle(::CatalogToggle) = true +_is_toggle(_) = false + +""" + _stop_call_code(code, stop) -> Union{String, Nothing} + +The `code` of a call `Detector(edge_length)` or `Detector(edge_length, stop)` (also qualified, e.g. +`BeamletOptics.Detector(…)`) with the second argument `stop`, or `nothing` for any other code. +""" +function _stop_call_code(code::AbstractString, stop::Bool) + ex = try + Meta.parse(code) + catch + return nothing + end + (ex isa Expr && ex.head === :call && _is_detector_name(ex.args[1])) || return nothing + # the positional arguments, after the name and before keywords + args = ex.args[2:end] + any(a -> a isa Expr && a.head in (:parameters, :kw), args) && return nothing + length(args) in (1, 2) || return nothing + return string(Expr(:call, ex.args[1], args[1], stop)) +end + +_is_detector_name(f) = f === :Detector || + (f isa Expr && f.head === :. && f.args[end] == QuoteNode(:Detector)) diff --git a/src/LiveEdit.jl b/src/LiveEdit.jl index a194a41..ce48610 100644 --- a/src/LiveEdit.jl +++ b/src/LiveEdit.jl @@ -437,6 +437,9 @@ _edit_param_rows(::LiveView, _, p::CatalogToggle, i::Int) = (CardRow(_edit_label CardWidget(Toggle; name = _edit_toggle(i), value = (gui, o) -> _catalog_value(p, _edit_strings(gui, o)[i]), on = (gui, o, v) -> _toggle_edit!(gui, o, i, v))),) +# A detector is switched on its card in place, see `_set_stop!`: "Apply" keeps its current state, +# which the switch writes into the inputs as well +_edit_param_rows(::LiveView, ::BMO.Detector, ::CatalogToggle, ::Int) = () _edit_param_rows(::LiveView, _, p::CatalogGlass, i::Int) = ( CardRow(_edit_label(p.name), CardWidget(Menu; name = _edit_menu(i), diff --git a/src/LiveExport.jl b/src/LiveExport.jl index 6cd7d56..e5d006e 100644 --- a/src/LiveExport.jl +++ b/src/LiveExport.jl @@ -558,10 +558,37 @@ function _export_code(gui::LiveView) (top isa LiveClipPlane || top in added || top in cx.written || haskey(inside, top)) && continue n += _export_pose_lines!(lines, gui, names, top) end + n += _export_stop_lines!(lines, gui, names, added) n == 0 && push!(lines, "", "# no changes") return join(lines, "\n") * "\n", n end +""" + _export_stop_lines!(lines, gui, names, added) -> n + +Appends a `BeamletOptics.stop!(name, stop)` to the `lines` of `export_changes` for each detector of +the `gui` that was switched on its card (see `_set_stop!`) and stops the beams now or not unlike at +the start of the view, except for one that was `added` in the window, whose code has the state. +Returns the number `n` of such detectors. +""" +function _export_stop_lines!(lines, gui::LiveView, names, added) + comp = gui.components + switched = String[] + for (d, stop0) in comp.stops0 + (d in added || _has(comp.removed, d) || BMO.stop(d) == stop0) && continue + name = get(names, d, nothing) + isnothing(name) || push!(switched, "BeamletOptics.stop!($name, $(BMO.stop(d)))") + end + isempty(switched) && return 0 + push!(lines, "", "# Detectors switched on their cards") + append!(lines, sort!(switched)) + return length(switched) +end + +# Whether the detector `obj` of the `gui` was switched on its card since the start, see `_set_stop!` +_stop_switched(gui::LiveView, @nospecialize(obj)) = + haskey(gui.components.stops0, obj) && BMO.stop(obj) != gui.components.stops0[obj] + """Copies `code` to the clipboard, returns `false` if there is no clipboard, e.g. headless.""" function _copy_to_clipboard(code::String) try @@ -738,6 +765,8 @@ function _export_construct_lines!(lines, gui::LiveView, names, @nospecialize(obj pose = String[] _export_pose_lines!(pose, gui, names, obj; heading = false, current) append!(lines, (_commented(line) for line in pose)) + # a detector of the script that was switched on its card, see `_set_stop!` + _stop_switched(gui, obj) && push!(lines, "# BeamletOptics.stop!($name, $(BMO.stop(obj)))") return false end push!(lines, _export_heading(gui, obj)) diff --git a/src/LiveView.jl b/src/LiveView.jl index 6004aed..3fe97af 100644 --- a/src/LiveView.jl +++ b/src/LiveView.jl @@ -514,6 +514,9 @@ Base.@kwdef mutable struct _ComponentState recording::Bool = true # the inputs of the page "Edit" of an object that are not applied yet, see `_edit_strings` const edits::IdDict{Any, Vector{String}} = IdDict{Any, Vector{String}}() + # whether a detector stopped the beams before its first switch on its card, for + # `export_changes`, see `_set_stop!` + const stops0::IdDict{Any, Bool} = IdDict{Any, Bool}() placement::Any = nothing # the source that is being aimed with the mouse, see `_start_aim!` aim::Any = nothing diff --git a/test/TestLiveDetectorStop.jl b/test/TestLiveDetectorStop.jl new file mode 100644 index 0000000..cef210a --- /dev/null +++ b/test/TestLiveDetectorStop.jl @@ -0,0 +1,122 @@ +module TestLiveDetectorStop + +using GLMakie, BeamletOptics, BeamletOpticsGUI +using Test + +const BMO = BeamletOptics +const GUI = BeamletOpticsGUI + +@testset "Switch \"stop beams\" on the card of a detector" begin + + # Each change is solved at once, see `TestLiveView.jl`; no card of a detector is pinned at start + _live_view(args...; kwargs...) = + live_view(args...; merge((; trace_budget = Inf, throttle = false, detectors = []), kwargs)...) + _entry(name) = only(e for e in component_catalog() if e.name == name) + _defaults(entry) = String[GUI._catalog_string(p) for p in entry.params] + _undo!(gui) = GUI._undo!(gui.controls) + _redo!(gui) = GUI._redo!(gui.controls) + # A detector at `y` on the beam along +y, and the beam + function _detector(y; len = 10e-3) + d = Detector(len) + translate3d!(d, [0, y, 0]) + return d + end + _beam() = Beam([0.0, 0, 0], [0.0, 1, 0], 632.8e-9) + _targets(beam) = Any[BMO.object(BMO.intersection(r)) for r in BMO.rays(beam) if !isnothing(BMO.intersection(r))] + _in(x, xs) = any(y -> y === x, xs) + + @testset "the row of the card" begin + rows = card_rows(Detector(10e-3)) + @test count(r -> any(c -> c isa CardWidget && c.name === :stop && c.type === Toggle, r.cells), rows) == 1 + end + + @testset "a detector of the script, $layout" for layout in (:compact, :app) + front, back = _detector(0.1), _detector(0.2) + sys = System([front, back]) + beam = _beam() + gui = _live_view(sys => beam; layout, labels = Dict(front => "front")) + @test _in(front, _targets(beam)) && !_in(back, _targets(beam)) + # switched off: the same object in its system, and the beam passes onto the second detector + GUI._set_stop!(gui, front, false) + @test !BMO.stop(front) && _in(front, sys.objects) && GUI._label(gui, front) == "front" + @test isnothing(gui.trace.error) && _in(back, _targets(beam)) + # no code of its own: `export_changes` writes the switch, `export_script` a comment with it + @test occursin("\nBeamletOptics.stop!(front, false)\n", GUI._export_code(gui)[1]) + @test occursin("\n# BeamletOptics.stop!(front, false)\n", GUI._export_script_code(gui)) + # undo and redo switch it back and forth and solve again + @test _undo!(gui) + @test BMO.stop(front) && !_in(back, _targets(beam)) + @test occursin("# no changes", GUI._export_code(gui)[1]) + @test _redo!(gui) + @test !BMO.stop(front) && _in(back, _targets(beam)) + # the same state again changes nothing + GUI._set_stop!(gui, front, false) + @test _undo!(gui) && BMO.stop(front) + close(gui) + end + + @testset "outdated without auto tracing" begin + front, back = _detector(0.1), _detector(0.2) + beam = _beam() + gui = _live_view(System([front, back]) => beam; auto_trace = false) + GUI._set_stop!(gui, front, false) + @test gui.trace.stale && !_in(back, _targets(beam)) + close(gui) + end + + @testset "a detector of the catalog: its code and the inputs of the page \"Edit\"" begin + back = _detector(0.2) + sys = System([back]) + beam = _beam() + gui = _live_view(sys => beam) + c = GUI._catalog_component(_entry("Detector"), _defaults(_entry("Detector"))) + translate3d!(c.obj, [0, 0.1, 0]) + add_component!(gui, c.obj; origin = c.origin, label = "PD") + GUI._set_stop!(gui, c.obj, false) + origin = gui.components.origin[c.obj] + @test origin.code == "Detector(0.01, false)" && origin.strings[end] == "false" + # added in the window: its code has the state, no separate switch line + code = GUI._export_code(gui)[1] + @test occursin("PD = Detector(0.01, false)", code) && !occursin("stop!", code) + # the page "Edit" has no switch of its own + toggle = only(p for p in _entry("Detector").params if p isa GUI.CatalogToggle) + @test isempty(GUI._edit_param_rows(gui, c.obj, toggle, 2)) + @test _undo!(gui) + @test gui.components.origin[c.obj].code == "Detector(0.01, true)" + close(gui) + end + + @testset "the code of a call of Detector" begin + @test GUI._stop_call_code("Detector(0.02)", false) == "Detector(0.02, false)" + @test GUI._stop_call_code("Detector(0.02, false)", true) == "Detector(0.02, true)" + @test GUI._stop_call_code("BeamletOptics.Detector(0.02, true)", false) == + "BeamletOptics.Detector(0.02, false)" + @test isnothing(GUI._stop_call_code("Detector(0.02; stop = true)", false)) + @test isnothing(GUI._stop_call_code("RoundPlanoMirror(0.02, 0.005)", false)) + @test isnothing(GUI._stop_call_code("Detector(", false)) + # added with its code: the call takes the switch + gui = _live_view(System()) + d = _detector(0.1; len = 20e-3) + add_component!(gui, d; code = "Detector(0.02)") + GUI._set_stop!(gui, d, false) + @test gui.components.origin[d].code == "Detector(0.02, false)" + close(gui) + end + + @testset "in a second window" begin + sys = System() + gui = _live_view(sys => _beam()) + c = GUI._catalog_component(_entry("Detector"), _defaults(_entry("Detector"))) + translate3d!(c.obj, [0, 0.1, 0]) + add_component!(gui, c.obj; origin = c.origin, label = "PD") + new = open_system(gui, sys; display = false) + GUI._set_stop!(new, c.obj, false) + @test !BMO.stop(c.obj) + @test gui.components.origin[c.obj].code == "Detector(0.01, false)" + @test new.components.origin[c.obj].code == "Detector(0.01, false)" + close(new) + close(gui) + end +end + +end diff --git a/test/TestLiveEdit.jl b/test/TestLiveEdit.jl index 8fa02db..2e57110 100644 --- a/test/TestLiveEdit.jl +++ b/test/TestLiveEdit.jl @@ -212,15 +212,19 @@ const GUI = BeamletOpticsGUI @test BMO.stop(c.obj) && _in(c.obj, targets()) && !_in(back, targets()) @test c.origin.code == "Detector(0.01, true)" - # the page: the box of the edge length and the toggle of "stop beams" + # the page: the box of the edge length, no switch: the card switches the detector in place card = _show!(gui, c.obj, :edit) - toggle = GUI._card_widget(card, GUI._edit_toggle(2)) - @test toggle isa Toggle && toggle.active[] - # switched off, with a length typed without Enter, which the switch keeps - _type!(card, 1, "20") - toggle.active[] = false + @test isnothing(GUI._card_widget(card, GUI._edit_toggle(2))) + # a new length that is not applied yet, then the detector switched off on its page "Pose": + # the same object, whose code and inputs (also the pending one) take the switch + GUI._set_edit_string!(gui, c.obj, 1, "20") + GUI._set_stop!(gui, c.obj, false) + @test !BMO.stop(c.obj) && _in(c.obj, sys.objects) @test GUI._edit_strings(gui, c.obj) == ["20", "false"] - @test _in(c.obj, sys.objects) + @test gui.components.origin[c.obj].code == "Detector(0.01, false)" + @test _in(c.obj, targets()) && _in(back, targets()) + # "Apply" builds the new length with the switch as it is + _show!(gui, c.obj, :edit) notify(GUI._card_widget(_card(gui), :edit_apply).clicks) new = only(o for o in sys.objects if o !== back) @test new !== c.obj && !BMO.stop(new) && GUI._label(gui, new) == "PD" @@ -230,11 +234,12 @@ const GUI = BeamletOpticsGUI @test isnothing(gui.trace.error) && !gui.trace.stale @test _in(new, targets()) && _in(back, targets()) @test !isnothing(BMO.hits(new)) && !isnothing(BMO.hits(back)) - @test !GUI._card_widget(_card(gui), GUI._edit_toggle(2)).active[] - # undo brings the detector back that stops the beam + # undo brings the old detector back, then switches it on again @test GUI._undo!(gui.controls) - @test _in(c.obj, sys.objects) && !_in(back, targets()) + @test _in(c.obj, sys.objects) && !BMO.stop(c.obj) + @test GUI._undo!(gui.controls) + @test BMO.stop(c.obj) && !_in(back, targets()) close(gui) end @@ -257,16 +262,14 @@ const GUI = BeamletOpticsGUI # the selection is elsewhere: "Apply" on the pinned card gui.controls.selected[] = nothing GUI._update_cards!(gui) - GUI._card_widget(card, GUI._edit_toggle(2)).active[] = false + _type!(card, 1, "15") notify(GUI._card_widget(card, :edit_apply).clicks) new = only(o for o in sys.objects if o !== back) - @test new !== c.obj && !BMO.stop(new) - # the card is pinned to the new detector, still on the page "Edit", with its switch off + @test new !== c.obj && gui.components.origin[new].code == "Detector(0.015, true)" + # the card is pinned to the new detector, still on the page "Edit" @test isempty(pinned(c.obj)) card = only(pinned(new)) @test GUI._is_pinned(gui, new) && card.page === :edit - GUI._update_cards!(gui) - @test !GUI._card_widget(card, GUI._edit_toggle(2)).active[] layout === :app && @test length(gui.layout.inspector.pinned) == (where === :docked) # a pinned card keeps another page, e.g. "Results" GUI._set_page!(gui, card, :results) diff --git a/test/TestPrecompileSession.jl b/test/TestPrecompileSession.jl index 046dd8a..70353b0 100644 --- a/test/TestPrecompileSession.jl +++ b/test/TestPrecompileSession.jl @@ -42,12 +42,11 @@ using Test empty!(names) ext._close!(ext._catalog_session(step)...) @test isempty(failed) - # each entry is placed, dragged and removed; one with a switch is also switched on its - # page "Edit", e.g. the detector - switched = count(e -> any(p -> p isa BeamletOpticsGUI.CatalogToggle, e.params), component_catalog()) - @test switched >= 1 + # each entry is placed, dragged and removed; one with a switch on its page "Edit" is also + # switched there (the detector has its switch on its card instead) + switched = count(n -> occursin("edit switch", n), names) @test length(names) == 3 * length(component_catalog()) + switched - @test any(n -> occursin("edit switch", n), names) + @test !any(n -> occursin("Detector: edit switch", n), names) end @testset "empty window, $layout" for layout in (:compact, :app)