1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! Wires `super::super::retarget` (the "Retarget for material" proposal
//! builder/applier) and `ui/components/retarget_dialog.slint` into the Edit tab --
//! `setup_*` functions, one per `RetargetDialog`/`EditorView` callback, mirroring
//! `solve_actions`'s Optimize callbacks in shape.
//!
//! # Where the target material comes from
//!
//! [`material::resolve_target_selection`] resolves the proposal's TARGET from
//! `RetargetModel`'s OWN `target_material_index`/`target_ri_override_text`
//! properties, NOT from `state.design.material` or the design settings panel's
//! combo: this dialog owns its target end to end, seeded from the design's current
//! material on `open()` (see [`proposal::setup_retarget_open_callback`]) and changed
//! only by this dialog's own picker. `retarget::apply` still only ever produces
//! `Edit::RetargetAngles` -- see [`apply::setup_retarget_apply_callback`] for how a
//! target material change actually reaches `design.material`.
//!
//! # `RetargetMode::Optimize` runs off the UI thread
//!
//! Shift mode is pure angle arithmetic and stays synchronous. `RetargetMode::Optimize`
//! runs a real `optimize_design` search, so
//! [`proposal::setup_retarget_proposal_changed_callback`] hands it to
//! [`super::super::optimize_solve::spawn_optimize_solve`] -- the exact same
//! worker/cancel/progress machinery the standalone Optimize panel uses
//! (`solve_actions::setup_optimize_callback`) -- rather than blocking the UI thread for
//! however long the search takes. [`RETARGET_ASYNC`] is this dialog's own
//! module-local run tracker: deliberately NOT a new `EditorState` field (that group is
//! owned elsewhere) and NOT threaded through any `setup_retarget_*` call site (`gui::
//! editor::mod`'s wiring is likewise owned elsewhere) -- a `thread_local!` is sound
//! here because Slint's event loop is single-threaded, exactly like `EditorState`'s own
//! `RefCell`. The worker's completion handler is `Send` (a hard requirement of
//! [`super::super::optimize_solve::spawn_optimize_solve`]'s own signature) so it cannot
//! capture `Rc<RefCell<EditorState>>` directly; it stashes the finished proposal into
//! [`RETARGET_ASYNC`] instead, and [`apply::setup_retarget_apply_callback`] reads it
//! back out on the main thread exactly like it already reads
//! `EditorState::pending_retarget` for a Shift-mode proposal.
//!
//! # Slint-free view-model split, for testability
//!
//! [`proposal_view::retarget_view`]/[`proposal_view::row_view`]/
//! [`material::resolve_target_selection`]/[`apply::apply_pending_retarget`] take and
//! return only plain Rust types, so this group's `tests` module can exercise the
//! actual decision logic directly -- `proposal_view::push_retarget_view`/
//! `proposal_view::push_target_readout` are the only two functions that touch a
//! Slint type, thin enough not to need tests.
//!
//! # Module layout
//!
//! Split by concern: [`material`] (target material resolution/display),
//! [`proposal_view`] (the Slint-free proposal view model and its `push_*`
//! renderers), [`proposal`] (the open/proposal-changed callbacks and Shift mode's
//! own synchronous rebuild), [`optimize_run`] (`RetargetMode::Optimize`'s off-thread
//! search), [`apply`] (committing a pending proposal, and closing the dialog), and
//! [`snapshot`] (the separate "Snapshot Design"/"Compare to Snapshot" feature that
//! also lives in this dialog). [`RETARGET_ASYNC`] and [`apply_ghost_preview_or_revert`]
//! are shared by more than one of those siblings, so they stay here rather than in
//! any one of them.
pub use ;
pub use ;
pub use setup_snapshot_callbacks;
use ;
use crate::;
use Design;
use ComponentHandle;
use ;
thread_local!
/// [`RETARGET_ASYNC`]'s payload.
/// Shows `design` retargeted by `proposal` as a ghost overlay
/// in the shared solid viewport, iff `RetargetModel.preview_enabled` is on AND
/// `proposal` is `Some` (a `None` proposal -- an anchored-tier refusal, a solve
/// error -- has nothing to preview). Returns whether a ghost was actually shown;
/// the caller resubmits the real, live design through the ordinary
/// [`view::submit_preview_replan`] path when it wasn't (see both call sites).
///
/// Builds the candidate via [`Design::apply_edit`] on a clone -- explicitly
/// documented as safe for exactly this ("usable standalone by a caller that wants
/// edit/undo without a `History` stack") -- rather than
/// [`super::super::state::EditorState::apply`], so this never touches
/// `History`/`is_dirty`/the design generation: a preview must never look like a
/// real edit to anything else in this crate.