Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/src/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,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 |
| Detectors | Detector, with the switch "stop beams": off, the beams pass it unchanged (`Detector(edge_length, false)`) and reach the components behind it |

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
Expand Down
37 changes: 37 additions & 0 deletions ext/BeamletOpticsGUIGLMakieExt/session.jl
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,25 @@ function _session(step, layout::Symbol, fixture::_Fixture = _fixture())
# the form of a lens with surfaces: the last one as an asphere, whose fields come with the next
# frame; the lens is placed with the button of the window, which closes when it is dropped
aspheric = Ref{Any}(nothing)
# the form of an entry with a switch, e.g. the detector: a click switches it
step("catalog: switch") do
widget = GUI._catalog_widget(GUI._catalog_window(gui))
i = findfirst(e -> any(p -> p isa GUI.CatalogToggle, e.params), widget.entries)
isnothing(i) && return nothing
k = findfirst(==(widget.entries[i].group), widget.groups)
_click!(gui, _rect_center(widget.group_buttons[k].box))
frame!()
j = findfirst(tile -> first(tile) == i, widget.tile_buttons)
_check(!isnothing(j), "the click did not choose the group of the entry with a switch")
_click!(gui, _rect_center(last(widget.tile_buttons[j]).box))
frame!()
_check(widget.entry == i, "the click did not choose the entry with a switch")
s = findfirst(p -> p isa GUI.CatalogToggle, widget.entries[i].params)
before = GUI._catalog_strings(widget)[s]
_click!(gui, _rect_center(widget.inputs[s]))
frame!()
_check(GUI._catalog_strings(widget)[s] != before, "the click did not switch the toggle")
end
step("catalog: aspheric surface") do
widget = GUI._catalog_widget(GUI._catalog_window(gui))
i = findfirst(e -> any(p -> p isa GUI.CatalogSurface, e.params), widget.entries)
Expand Down Expand Up @@ -1121,6 +1140,24 @@ 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"
s = findfirst(p -> p isa GUI.CatalogToggle, entry.params)
if !isnothing(s)
step("catalog, $(entry.name): edit switch + apply") do
gui.controls.selected[] === obj || select!(gui, obj)
frame!()
card = last(filter(c -> GUI._card_object(gui, c) === obj, GUI._edit_cards(gui)))
GUI._set_page!(gui, card, :edit)
frame!()
_click!(gui, _rect_center(GUI._card_widget(card, GUI._edit_toggle(s))))
frame!()
notify(GUI._card_widget(card, :edit_apply).clicks)
settle!()
# the new object takes the place of the old one and is selected
_check(gui.controls.selected[] !== obj, "the $(entry.name) was not built again")
obj = gui.controls.selected[]
end
end
step("catalog, $(entry.name): delete") do
gui.controls.selected[] === obj || select!(gui, obj)
_key!(gui, Keyboard.delete)
Expand Down
4 changes: 3 additions & 1 deletion skills/beamletopticsgui/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ a radius [mm, sign of BeamletOptics, `Inf` for a plane], and for an even asphere
constant `k` and the coefficients A4 to A16 in mm^(1 - order) as on a data sheet (A2 is zero). A
lens with an asphere is built and exported as the `Lens`, `DoubletLens` or `TripletLens` of its
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. "Place"
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"
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
Expand Down
20 changes: 18 additions & 2 deletions src/API.jl
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,22 @@ struct CatalogSurface
radius::Float64
end

"""
CatalogToggle(name, default::Bool; keyword = nothing)

A switch of an entry of the built-in catalog (internal): the catalog and the page "Edit" show a
`Toggle`, and the constructor gets its state as a `Bool`, e.g. the `stop` of a `Detector`. Its text
(see `_catalog_value`) is `"true"` or `"false"`. `name` and `keyword` as for [`CatalogParam`](@ref).
"""
struct CatalogToggle
name::String
default::Bool
keyword::Union{Nothing, Symbol}
end

CatalogToggle(name::AbstractString, default::Bool; keyword::Union{Nothing, Symbol} = nothing) =
CatalogToggle(String(name), default, keyword)

"""
CatalogEntry(name, constructor; group = "Components", params = [], code_name = string(nameof(constructor)), icon = nothing, source = false)

Expand Down Expand Up @@ -692,7 +708,7 @@ struct CatalogEntry
name::String
group::String
constructor::Any
params::Vector{Union{CatalogParam, CatalogGlass, CatalogSurface}}
params::Vector{Union{CatalogParam, CatalogGlass, CatalogSurface, CatalogToggle}}
code_name::String
icon::Union{Nothing, Symbol, Makie.BezierPath}
source::Bool
Expand All @@ -704,7 +720,7 @@ function CatalogEntry(name::AbstractString, constructor; group::AbstractString =
# an unknown name throws
icon isa Symbol && _icon(icon)
return CatalogEntry(String(name), String(group), constructor,
Union{CatalogParam, CatalogGlass, CatalogSurface}[params...], String(code_name), icon, source)
Union{CatalogParam, CatalogGlass, CatalogSurface, CatalogToggle}[params...], String(code_name), icon, source)
end

"""
Expand Down
23 changes: 23 additions & 0 deletions src/LiveCards.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,29 @@ _floating_cards(gui::LiveView, @nospecialize(obj)) = filter(c -> c.pinned && c.o
"""Returns `true` if a pinned card of the `gui` floats next to `obj` in the 3D view."""
_is_floating(gui::LiveView, @nospecialize(obj)) = any(c -> c.pinned && c.obj === obj, gui.cards.all)

"""
_take_pins!(gui, old) -> pins
_give_pins!(gui, pins, new)

Hand the cards pinned to `old` over to `new`, which takes its place, see `_replace!`:
`_take_pins!` detaches them from `old` before it is removed, which would unpin them, and
`_give_pins!` pins them to `new` once it is in the view. A floating card keeps its spot, its page,
if `new` has it, and whether it is collapsed; until then it is pinned to no object and hidden. A
layout that docks pinned cards hands those over as well, see `_take_pins!(::AppView, old)`.
"""
function _take_pins!(gui::LiveView, @nospecialize(old))
floating = _floating_cards(gui, old)
foreach(c -> c.obj = nothing, floating)
return (; floating, docked = ())
end

function _give_pins!(gui::LiveView, pins, @nospecialize(new))
# the card builds its rows for `new`, see `_update_card!`
foreach(c -> (c.obj, c.key, c.pose) = (new, nothing, nothing), pins.floating)
isempty(pins.floating) || _update_cards!(gui)
return nothing
end

"""
_float!(gui, obj)
_dock!(gui, obj)
Expand Down
35 changes: 31 additions & 4 deletions src/LiveCatalog.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ The default of the parameter `p` as a value, see `_catalog_args`: the number of
"""
_catalog_default(p::CatalogParam) = p.default
_catalog_default(p::CatalogGlass) = p.default == _GLASS_CONSTANT ? p.n : p.default
_catalog_default(p::CatalogToggle) = p.default

"""
The argument of the constructor for the value `v` of the parameter `p`: the number (an `Int` for a
Expand All @@ -26,11 +27,13 @@ index `v`, a `ConstantRefractiveIndex`.
_catalog_arg(p::CatalogParam, v::Real) = p.integer ? Int(v) : Float64(v)
_catalog_arg(::CatalogGlass, v::AbstractString) = _glass(v)
_catalog_arg(::CatalogGlass, v::Real) = ConstantRefractiveIndex(Float64(v))
_catalog_arg(::CatalogToggle, v::Bool) = v

"""The argument of the constructor for the value `v` of the parameter `p` as Julia code, see `_catalog_arg`."""
_catalog_arg_code(p::CatalogParam, v::Real) = repr(_catalog_arg(p, v))
_catalog_arg_code(::CatalogGlass, v::AbstractString) = _glass_code(_glass(v))
_catalog_arg_code(p::CatalogGlass, v::Real) = _glass_code(_catalog_arg(p, v))
_catalog_arg_code(::CatalogToggle, v::Bool) = repr(v)

"""
_catalog_args([f,] entry, values) -> (positional, keywords)
Expand All @@ -56,7 +59,7 @@ end
_catalog_args(entry::CatalogEntry, values) = _catalog_args(_catalog_arg, entry, values)

# The keyword as which the parameter `p` is passed, `nothing` for a positional argument
_catalog_keyword(p::Union{CatalogParam, CatalogGlass}) = p.keyword
_catalog_keyword(p::Union{CatalogParam, CatalogGlass, CatalogToggle}) = p.keyword

"""
_catalog_object(entry, values) -> Union{ObjectOrGroup, AbstractBeam, AbstractBeamGroup}
Expand Down Expand Up @@ -125,6 +128,7 @@ glass or its constant refractive index.
"""
_catalog_string(p::CatalogParam) = _catalog_number_string(p.default / p.scale)
_catalog_string(p::CatalogGlass) = p.default == _GLASS_CONSTANT ? _catalog_number_string(p.n) : p.default
_catalog_string(p::CatalogToggle) = string(p.default)

"""
_catalog_value(p, s) -> Union{Float64, String}
Expand All @@ -139,6 +143,9 @@ whole number.
A [`CatalogGlass`](@ref) gives the name of the glass if `s` is one of [`catalog_glasses`](@ref),
otherwise the constant refractive index that `s` holds, `p.n` for an empty box.

A `CatalogToggle` gives `true` for `"true"` and `false` for `"false"`, its default for an empty
text.

Throws an `ArgumentError` for a text that is neither.
"""
function _catalog_value(p::CatalogParam, s::AbstractString)
Expand All @@ -165,6 +172,14 @@ function _catalog_value(p::CatalogGlass, s::AbstractString)
return x
end

function _catalog_value(p::CatalogToggle, s::AbstractString)
text = strip(s)
isempty(text) && return p.default
v = tryparse(Bool, text)
isnothing(v) && throw(ArgumentError("invalid input \"$text\" for $(p.name), enter true or false"))
return v
end

"""
_catalog_component(entry, strings) -> (; obj, origin)

Expand Down Expand Up @@ -286,7 +301,8 @@ theme tokens `theme`, from top to bottom:
`Textbox` per number, per glass its menu and, for "constant", the box of the refractive
index, as `(; menu, box)`, and per surface its menu, the box of its radius and, for "aspheric",
the boxes of its conic constant and of its coefficients, as
`(; menu, radius, conic, coefficients)`, see `_fill_catalog_surfaces!`. `boxes` and `menus` hold
`(; menu, radius, conic, coefficients)`, see `_fill_catalog_surfaces!`, and a `Toggle` per
switch (`CatalogToggle`). `boxes` and `menus` hold
all of them; the `menus` also hold the menus of the presets of the numbers, which set their
boxes, see `_catalog_preset_menu!`.
- `place`: the button "Place"
Expand Down Expand Up @@ -332,6 +348,7 @@ function _catalog_input_string(p::CatalogGlass, input::NamedTuple)
# "constant" was chosen and its box is not built yet, see `_flush_catalog_form!`
return isnothing(input.box) ? _catalog_number_string(p.n) : String(input.box.displayed_string[])
end
_catalog_input_string(::CatalogToggle, input::Toggle) = string(input.active[])

# The text of a surface from its menu and its boxes, see `_surface_string`
function _catalog_input_string(::CatalogSurface, input::NamedTuple)
Expand Down Expand Up @@ -851,8 +868,9 @@ its inputs (see `_catalog_strings`): first the surfaces (see `_fill_catalog_surf
numbers, each with its name, a `Textbox` and its unit, in two columns from the number of its style on (see `_catalog_style`), then the glasses, each
with its name and the menu of the glasses and "constant", for which a box takes the refractive
index, right of the menu or below it. A number with presets has their menu right of its unit, or
below its box where the box of "constant" is below its menu, see `_catalog_preset_menu!`. The boxes
and the menus take the keyboard like those of the controls, see `_register_widget!`.
below its box where the box of "constant" is below its menu, see `_catalog_preset_menu!`. Last the
switches (`CatalogToggle`), each with its name and a `Toggle`. The boxes and the menus take the
keyboard like those of the controls, see `_register_widget!`.
"""
function _fill_catalog_form!(gui::LiveView, w::_CatalogWidget, strings)
params = _catalog_entry(w).params
Expand Down Expand Up @@ -933,6 +951,15 @@ function _fill_catalog_form!(gui::LiveView, w::_CatalogWidget, strings)
end)
end
end
toggles = findall(p -> p isa CatalogToggle, params)
if !isempty(toggles)
grid = _catalog_form_grid(w.form[part += 1, 1])
for (row, i) in enumerate(toggles)
Label(grid[row, 1], params[i].name; label...)
w.inputs[i] = Toggle(grid[row, 2]; active = _catalog_value(params[i], strings[i]),
_card_style(t, Toggle)..., halign = :left)
end
end
foreach(b -> _register_widget!(gui, b), w.boxes)
foreach(m -> _register_widget!(gui, m), w.menus)
# drawn with the cameras of their sizes like the widgets of a new card, see `_settle_cards!`
Expand Down
8 changes: 5 additions & 3 deletions src/LiveCatalogEntries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ _entry_code(::Union{typeof(_line_source), typeof(_fan_source)}, entry::CatalogEn

The entries of [`component_catalog`](@ref): the sources of BeamletOptics and all its components
with a constructor of numbers and glasses, with the defaults of one inch optics. Only these
arguments are parameters, the others keep the defaults of the constructor, e.g. the `stop` of a
`Detector` and the `thickness` and the `hole_diameter` of the curved mirrors. The radii of curvature
arguments are parameters, the others keep the defaults of the constructor, e.g. the `thickness` and
the `hole_diameter` of the curved mirrors. The `stop` of a `Detector` is a switch (`CatalogToggle`)
"stop beams", on by default: off, the beams pass the detector unchanged. The radii of curvature
follow BeamletOptics: positive if the center of the surface lies behind it (towards +y), i.e.
`R1 > 0` and `R2 < 0` for a biconvex lens, `Inf` for a plane surface of a `SphericalLens`.

Expand Down Expand Up @@ -203,7 +204,8 @@ function _builtin_catalog()
("Round filter", :polarizer, RoundPolarizationFilter, diameter),
("Linear polarizer", :linear_polarizer, RoundLinearPolarizer,
diameter, len("front thickness", 1e-3), len("back thickness", 1e-3), CatalogGlass()))
# off: the beams pass the detector unchanged, see `Detector`
group!("Detectors",
("Detector", :detector, Detector, len("edge length", 10e-3)))
("Detector", :detector, Detector, len("edge length", 10e-3), CatalogToggle("stop beams", true)))
return entries
end
34 changes: 30 additions & 4 deletions src/LiveEdit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ end
# The names of the widgets of the parameter `i`: its box and, of a glass, its menu
_edit_box(i::Int) = Symbol(:edit_, i)
_edit_menu(i::Int) = Symbol(:edit_glass_, i)
# ... of a switch, its toggle
_edit_toggle(i::Int) = Symbol(:edit_toggle_, i)
# Of a surface: its menu, and the box of its field `k`, see `_edit_surface`: the radius in the box
# of the parameter, then the conic constant and the coefficients
_edit_surface_menu(i::Int) = Symbol(:edit_surface_, i)
Expand Down Expand Up @@ -153,7 +155,8 @@ end
Puts the component or source `new` with its `origin` in the place of `old` in the `gui`: `new` gets
the pose of `old`, i.e. it is moved and rotated from its pose as constructed like `old` was from
its own, then `old` is removed and `new` is added to the same system with the label of `old` and
selected, and the card of the selection shows its page "Edit" again. The replacement is one action
selected, and the card of the selection shows its page "Edit" again. The cards pinned to `old`,
floating or docked, are pinned to `new`, see `_take_pins!`. The replacement is one action
of the undo history: undo brings `old` back, redo `new`.
"""
function _replace!(gui::LiveView, old, new, origin)
Expand All @@ -173,9 +176,12 @@ function _replace!(gui::LiveView, old, new, origin)
new_snap = Ref{Any}(_replacement_snapshot(gui, old, new, origin, old_snap))
old_state = Ref{Any}(old_snap)
swap!(out, out_state, in, in_state) = begin
# the cards pinned to `out` stay, pinned to `in`
pins = _take_pins!(gui, out)
out_state[] = _remove_unrecorded!(gui, out)
delete!(comp.edits, out)
_restore!(gui, in, in_state[])
_give_pins!(gui, pins, in)
_show_edit_page!(gui, in)
return nothing
end
Expand All @@ -185,12 +191,16 @@ function _replace!(gui::LiveView, old, new, origin)
return new
end

"""Shows the page "Edit" of the cards of `obj` again, e.g. after it replaced the object of the page."""
"""
Shows the page "Edit" of the cards of `obj` again, e.g. after it replaced the object of the page.
A pinned card keeps its page, see `_give_pins!`.
"""
function _show_edit_page!(gui::LiveView, @nospecialize(obj))
_update_cards!(gui)
_update_inspector!(gui)
for c in _edit_cards(gui)
(_card_object(gui, c) === obj && :edit in _card_pages(gui, obj)) && _set_page!(gui, c, :edit)
(!c.pinned && _card_object(gui, c) === obj && :edit in _card_pages(gui, obj)) &&
_set_page!(gui, c, :edit)
end
return nothing
end
Expand Down Expand Up @@ -266,6 +276,15 @@ function _choose_edit_glass!(gui::LiveView, @nospecialize(obj), p::CatalogGlass,
return nothing
end

# The toggle of the switch `i` changed to `v`
function _toggle_edit!(gui::LiveView, @nospecialize(obj), i::Int, v)
v isa Bool || return nothing
# The card shows its values again after an input: keep what was typed into its boxes
_read_edit_inputs!(gui, obj)
_set_edit_string!(gui, obj, i, string(v))
return nothing
end

# Enter in the box of the parameter `i`: its input, then "Apply"
function _enter_edit!(gui::LiveView, @nospecialize(obj), i::Int, s)
# the other boxes as they are shown, then the entered one
Expand Down Expand Up @@ -357,7 +376,8 @@ The rows of the parameter `p`, the input `i`, on the page "Edit" of `obj`: a box
menu and the box of a constant refractive index for a glass, and for a surface its menu
("spherical", "aspheric"), the box of its radius and, only while "aspheric" is chosen, the boxes of
the conic constant `k` and of the coefficients A4, A6, ... [mm^(1 - order)], two in a row. The rows
of a surface thus depend on its input, see `_rebuild_edit_rows!`.
of a surface thus depend on its input, see `_rebuild_edit_rows!`. A switch (`CatalogToggle`) has a
`Toggle`.
"""
_edit_param_rows(::LiveView, _, p::CatalogParam, i::Int) = (CardRow(_edit_label(p.name),
CardWidget(Textbox; name = _edit_box(i), placeholder = " ", width = 76,
Expand All @@ -382,6 +402,12 @@ function _edit_param_rows(gui::LiveView, @nospecialize(obj), p::CatalogSurface,
return Tuple(rows)
end

# The toggle of a switch: its state is the input, which "Apply" builds, like the menu of a glass
_edit_param_rows(::LiveView, _, p::CatalogToggle, i::Int) = (CardRow(_edit_label(p.name),
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))),)

_edit_param_rows(::LiveView, _, p::CatalogGlass, i::Int) = (
CardRow(_edit_label(p.name), CardWidget(Menu; name = _edit_menu(i),
options = String[_glass_names()..., _GLASS_CONSTANT], width = 128,
Expand Down
Loading
Loading